Article

Thinking in BLoC: Mindful state design for cross-platform development

Last updated 
Dec 26, 2024
 min read

When I first learned about BLoC, I thought it was just about events and their resulting states. For example, press a button, trigger an event, and the UI updates based on the state. This straightforward approach worked for simple apps, but it began to fall apart when I had to manage more complex UIs.

Take a screen with multiple filters and a list that refreshes locally, for instance. There were no API calls involved, but the interaction between UI elements demanded more thoughtful state management. It quickly became clear that my understanding of Bloc wasn’t scalable for real-world apps.

When I discussed this with colleagues, I realized many had faced similar struggles early on. The issue wasn’t Bloc itself—it was how we were approaching state design.

So, how do you avoid these pitfalls? The answer lies in:

Designing states mindfully and recognizing patterns that naturally emerge from thoughtful state management.

The Shift: A mindful approach to state design

The turning point for me was recognizing that BLoC is more than just an event-to-state pipeline. It’s a tool to manage state as a concept, helping you create a predictable, scalable, and maintainable application. But this requires a mindset shift.

You need to think about:

  • What states does your UI truly need?
  • How will those states evolve over time?
  • What patterns emerge from your state transitions?

Let me break this down with an analogy: Think of the state as a piece of fabric in a tailor’s shop. Each time you update the fabric, you create a new shirt. In your app, every state transition is like swapping out that fabric—the UI reflects a completely new “shirt” every time.

Flutter’s core principle says it best:

The UI is a function of state.

Designing scalable BLoC states for cross-platform apps

Here’s where most developers hit roadblocks: How do you design states that avoids rework and scales easily when new features are added?

At Aubergine, we’ve adopted a simple but effective approach: Visualize the UI as a chain of snapshots. Each snapshot represents a specific state of the UI. By focusing on these snapshots, we ensure that our state design remains clear and purpose-driven.

This means every state change determines how your build() method updates the UI. Once you grasp this, you’ll start to see the state as the foundation of your app’s design, not just a technical detail.

Effective strategies for state design in Flutter BLoC

Here are few key strategies we follow when designing BLoC states:

1. Expose only essential states

Sometimes, your BLoC may need to interact with other parts of the app, requiring its state to be accessible globally. In such cases, ensure you expose only the essential parts of the state. Keep the exposed states minimal and focused to maintain clarity and encapsulation, avoiding unnecessary details or sensitive information.

Example: Authentication
An authentication feature might need to be exposed:

  • Authenticated: The user is logged in.
  • Unauthenticated: The user isn’t logged in, with optional details like “session expired” or “invalid credentials.”

Avoid overloading this BLoC with transient states like loading or errors; these can often be handled more effectively at the UI layer. By keeping states minimal and focused, you create a cleaner interface for interacting with the Bloc.

2. Use the individual state strategy

For features tied to UI events, design states that reflect each possible outcome.

Example: List with API Calls

Imagine an API call that fetches a list of items. The UI might need to handle:

  1. Loading: Show a blocking loader.
  2. Error: Display an error message.
  3. Loaded: Render the list.

In this case:

  • Use BlocListener for transient states like showing a loader or error dialog.
  • Use BlocBuilder to manage persistent states like rendering the list.

Separating transient and persistent states keeps your implementation clean and ensures the UI handles each state appropriately.

3. Use a central state

This strategy involves collating all dynamic state variables into a single state object. It’s particularly useful for complex UIs where multiple elements need to interact.
Example: List with Filters and Pagination
Let’s say you have:

  • A list of items to display.
  • Filters were applied locally to refine the list.
  • Pagination to load more items.
  • Error screens for failure scenarios.

By centralizing these dynamic variables into one state, you ensure the UI always reflects the latest, unified state of the app. This approach is also effective for multi-screen flows like onboarding, where you want to manage navigation and user progress cohesively.

4. Avoid using setState() with BLoC

While not a hard rule, we typically avoid mixing setState() with Bloc-managed states.

Why?

  • It can create conflicts between the widget-managed state and the Bloc-managed state.
  • It becomes harder to maintain and debug as the app grows.

Instead, rely on Bloc’s state stream to manage changes. This keeps your code consistent and predictable, especially in larger applications.

How this approach helps

By following these strategies, you’ll find that:

  • State management becomes intuitive. Thinking in snapshots helps you anticipate and design for every UI change.
  • Code remains adaptable. When new features are added, your state design scales with minimal rework.
  • Collaboration improves. Clear, minimal states make it easier for teams to understand and build on your work.

You can always use these strategies in combination as per your requirement. You don’t have to use one at a time.

Why BLoC aligns with our principles

BLoC aligns seamlessly with our core development principles, facilitating mindful state design and supporting scalability and maintainability.

  • Facilitates Mindful State Design: BLoC encourages developers to think deeply about the states their application needs, promoting intentional and purposeful state management.
  • Supports Scalability: Its event-driven architecture allows for easy addition of new features without disrupting existing functionality.
  • Enhances Maintainability: Clear separation of concerns ensures that business logic is decoupled from the UI, making the codebase easier to manage and update.

The impact on cross-platform development

Mindful state management with BLoC has profound benefits for cross-platform app development, enhancing consistency, code reuse, and overall quality.

  • Consistency Across Platforms: BLoC ensures that state management logic remains uniform across different platforms (iOS, Android, Web), providing a consistent user experience.
  • Easier Code Sharing and Reuse: With a centralized state management system, we can reuse business logic across multiple platforms, reducing duplication and enhancing efficiency.
  • Simplified Debugging and Testing: A predictable state flow simplifies the debugging process and makes automated testing more straightforward, as states and events can be tested in isolation.

Advice to developers facing challenges

  • Start Simple. Begin with straightforward state definitions and gradually introduce complexity as needed. Avoid the temptation to over-engineer from the outset.
  • Embrace Consistency. Maintain consistent state management patterns throughout your application to reduce complexity and enhance maintainability.
  • Leverage BLoC’s Features Fully. Utilize BLoC’s full suite of tools, such as BlocListener and BlocBuilder, to manage different types of states effectively.
  • Continuously Refine Your Approach. Regularly review and refine your state management strategy to adapt to evolving application requirements and team dynamics.

Conclusion

BLoC isn’t just a state management tool—it’s a way of thinking about how your app evolves. By designing states with intention and focusing on UI outcomes, you can build apps that are not only scalable but also easier to maintain.

If you’ve struggled with BLoC in the past, ask yourself:

  • Are my states too complex?
  • Am I focusing on outcomes rather than transitions?

Try applying the snapshot approach in your next feature. And if you have a unique way of managing BLoC states, I’d love to hear about it in the comments. Let’s continue learning and improving together!

Authors

Harsh Soni

Associate Technical Lead
Harsh is a developer with about a decade (if not more) of expertise in building mobile apps. More of a doer, less of a talker, he likes to keep his mind occupied, and has an eye for understanding systems in their barebones, helping him be his creative best, impacting users' lives for the better. Enjoys building reusable & scalable systems, reading a book in a quiet beautiful place, or learning something new.

Tags

No items found.

Have a project in mind?

Read