HomeBlogTech UpdatesBeyond Clean Architecture: The Iceberg Pattern for Real-Time Flutter Apps with BlocSignal
Tech UpdatesSeptember 5, 20264 min

Beyond Clean Architecture: The Iceberg Pattern for Real-Time Flutter Apps with BlocSignal

Beyond Clean Architecture: The Iceberg Pattern for Real-Time Flutter Apps with BlocSignal ## Introduction to the Problem Why does traditional clean architecture not fit modern real-world applications? Most Flutter...

Beyond Clean Architecture: The Iceberg Pattern for Real-Time Flutter Apps with BlocSignal
Beyond Clean Architecture: The Iceberg Pattern for Real-Time Flutter Apps with BlocSignal - image 2

Beyond Clean Architecture: The Iceberg Pattern for Real-Time Flutter Apps with BlocSignal

Introduction to the Problem

Why does traditional clean architecture not fit modern real-world applications? Most Flutter tutorials follow Uncle Bob's advice and use the classic BLoC structure, but they demonstrate architecture exclusively with static REST requests or trivial counter apps. As soon as you start building a modern application based on data storage and powerful cloud services, such an architecture quickly turns into one of two anti-patterns:

  1. "Anemic Lasagna": Layers of intermediary classes that simply redirect method calls to the lower layer without transforming data, concluding invariants, or providing architectural protection.
  2. Super-complex Streams and Macros: Synchronizing multiple live cloud streams using intricate Rx cha

els, nested subscriptions, and state flags, which can lead to errors.

Reality of Real-Time and Iceberg Pattern

Reality of Cloud Applications

When working with cloud services like Firebase Cloud Firestore or Supabase, you need to manage real-time data streams. This means your architecture must be capable of handling simultaneous data changes and ensuring reliable synchronization between the client and server.

Problems with Classical Clean Architecture

Classical clean architecture with REST APIs can be too complex and inefficient for real-world applications. It requires large amounts of code and u

ecessary rituals, especially when it comes to simple data reading operations.

Introduction to Iceberg Pattern

Iceberg Pattern offers a solution to this problem by combining reactive signals and one-way BLoC facades to create an efficient architecture for real-world applications.

Detailed Description of Iceberg Pattern

Persisted Data

Iceberg Pattern provides persisted data, which are retained even after navigating between screens. This improves performance and reduces the number of server requests.

// Example of using persisted data
final cache = ValueNotifier<Map<String, dynamic>>({});

void updateCache(String key, dynamic value) {
  cache.value[key] = value;
}

void fetchFromCache(String key) {
  final cachedValue = cache.value[key];
  if (cachedValue != null) {
    // Use cached value
  } else {
    // Fetch value from data source
  }
}

Optimistic Mutations

Iceberg Pattern also provides support for optimistic mutations, where changes are displayed on the screen before they are confirmed by the server. This ensures a smoother user experience.

// Example of optimistic mutation
void optimisticUpdate(String key, dynamic newValue) async {
  // Update data on screen
  setState(() {
    cache.value[key] = newValue;
  });

  try {
    // Send request to server
    await server.update(key, newValue);
    // Update cache after successful request
    updateCache(key, newValue);
  } catch (e) {
    // Rollback changes on error
    setState(() {
      cache.value[key] = oldValue;
    });
  }
}

Asynchronous Reconciliation

Iceberg Pattern automatically synchronizes changes between the client and the server, ensuring correct recovery after errors and conflicts.

// Example of asynchronous reconciliation
Future<void> reconcileData() async {
  final localChanges = getLocalChanges();
  final serverChanges = await fetchServerChanges();

  // Handle conflicts and synchronize data
  await applyChanges(localChanges, serverChanges);
}

Practical Tips

Using ValueNotifiers for Data Caching

ValueNotifiers allow easy updating and synchronization of data across architecture layers.

final cache = ValueNotifier<Map<String, dynamic>>({});

Error Handling and Rollbacks

It is important always to have a rollback mechanism for changes in case of errors.

try {
  // Perform operation
} catch (e) {
  // Rollback changes
}

Automatic UI Updates

Use StreamBuilder or StreamProvider for automatic UI updates in response to data changes.

StreamBuilder(
  stream: dataStream,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text(snapshot.data.toString());
    } else if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    } else {
      return CircularProgressIndicator();
    }
  },
)

Modularity and Scalability

Iceberg Pattern supports modularity and scalability, allowing easy addition of new features and components.

Conclusion

Iceberg Pattern provides an effective solution for creating real-world Flutter applications with real cloud services. It combines the benefits of clean architecture and reactive signals, ensuring optimized user experience and reliable data synchronization.