Clean Architecture & State Management: Where Does Bloc Fit In?

A Retrospective on Building a Community-Driven History App with Flutter and .NET

As first-time Flutter developers, our six-week journey to create a community-driven history app—allowing users to discover local stories via maps and AR—was both exhilarating and daunting. With no prior experience in Flutter, we dove headfirst into experimentation, balancing rapid learning with the pressure to deliver a functional app. Using Flutter for the frontend and .NET 9 for the backend API, we adopted Clean Architecture to tame the chaos of unfamiliar territory.

Navigating state management with Riverpod and Flutter Stateful Widgets was a crucial part of our learning process. We explored where BLoC might have fit in and what it felt like to learn Flutter through trial and error. Each decision and challenge helped us understand Flutter more deeply, and while there were bumps along the way, the progress was incredibly rewarding.

Learning Flutter on the Fly: The Experimentation Phase

Flutter’s promise of cross-platform development initially drew us in, but the steep learning curve hit hard. Widgets, state management, and the Dart language were all new territory for us. The early days were filled with:

  • Endless documentation dives: Trying to grasp the difference between StatefulWidget and StatelessWidget.

  • Trial-and-error UI builds: Struggling with UI alignment and AR view integration.

  • State management confusion: Debating whether to use setState, Riverpod, BLoC, or even Provider.

Every error felt like a roadblock, but each solution became a milestone. For instance, realizing that setState was enough for a toggle button but inadequate for managing global app state was a key insight.

Through experimentation, we learned to embrace failure as part of the journey and a step toward progress.

"It's fine to celebrate success, but it is more important to heed the lessons of failure."

Clean Architecture: A Lifeline Amid Chaos

Clean Architecture’s layered structure became our anchor throughout the development process. We divided the app into:

  • Presentation Layer: Flutter widgets (built with shaky confidence).

  • Domain Layer: Use cases and entities (where the logic started making sense).

  • Data Layer: Repositories and .NET API integration (a familiar refuge for our backend experience).

This separation saved us from falling into the trap of spaghetti code. For instance, when our AR camera plugin crashed due to improper state handling, Clean Architecture allowed us to refactor the UI without breaking the business logic. That said, mapping Flutter’s ecosystem to these layers took countless iterations, and it was a constant challenge to align everything smoothly.

State Management: From Confusion to Clarity

Stateful Widgets: Baby Steps

We started with StatefulWidget for handling simple UI states, such as toggling the AR view. This allowed us to manage local state effectively in the beginning, but as the app grew in complexity, we realized we needed a more scalable solution for managing global state.

				
					// This is just a simple example
bool _showAR = false;  

void _toggleAR() {  
  setState(() => _showAR = !_showAR);  
}  
				
			

It worked, until we needed to share state across multiple screens. That’s when we realized StatefulWidget wasn’t enough for managing global state effectively, and we had to explore more advanced solutions like Riverpod to handle state across the app.

Riverpod: The "Aha!" Moment

Riverpod’s Provider felt alien at first, but its flexibility quickly won us over. After scrapping a BLoC prototype (which had too much boilerplate), we embraced Riverpod for managing global state. Its clean, declarative approach allowed us to keep things organized without getting bogged down by excessive code.

				
					// Final approach: Clean and scalable 
final userAuthProvider = 
    StateNotifierProvider<UserAuthNotifier, User?>((ref) { 
        return UserAuthNotifier(); 
}); 
				
			

BLoC: The Road Not Taken

We explored BLoC briefly but quickly retreated. The complexity of streams and events overwhelmed our novice Flutter brains. In hindsight, BLoC might have been a better fit for handling real-time AR features, but Riverpod’s simplicity aligned perfectly with our scrappy timeline, allowing us to move quickly and focus on getting the app up and running.

The Emotional Roller coaster of Learning Flutter

Imposter syndrome lingered, but small victories kept us pushing forward. Flutter’s hot reload became my personal best friend, allowing me to experiment fearlessly with the frontend. By week 6, I found myself debugging FutureBuilder issues with a sense of begrudging expertise.

  • Week 1: “How do I center a widget?!”

  • Week 3: “Wait, BuildContext is actually useful?”

  • Week 5: “We built a working AR view?!”

Each milestone, no matter how small, made all the struggles worth it.

Why Experimentation Mattered

  1. Adaptability: Forced us to critically compare tools like Riverpod and BLoC to find the right fit for our needs.
  2. Ownership: Every line of code felt earned, with each decision contributing to the app’s growth.
  3. Resilience: Errors stopped being scary and started feeling like puzzles to solve, making the process more rewarding.

Conclusion

Building this platform was more than just a project—it was a crash course in Flutter, Clean Architecture, and state management. As beginners, we stumbled, experimented, and learned by doing. Clean Architecture gave us structure, Riverpod simplified state management, and Flutter’s ecosystem empowered us to build something we’re genuinely proud of.

While BLoC remains a tool we might explore in the future, our journey taught us that the best architecture and state management solutions are those that fit your team’s skills and the needs of your project. For us, that meant embracing simplicity and learning through trial and error.

To fellow Flutter newcomers: expect chaos, celebrate small wins, and remember—every error is a step closer to mastery.

Leave a Reply

Your email address will not be published. Required fields are marked *

Table of Contents

SYNNOVATION