Through my experience with Flutter state management, I've learned valuable lessons about Bloc, Cubit, and particularly MultiBlocProvider implementations - often through practical challenges in real projects. While MultiBlocProvider appears straightforward at first, understanding its proper usage can make a real difference in your app's performance and maintainability. I'd like to share these insights to help you build better applications while avoiding some common pitfalls I've encountered along the way
A Stack Overflow answer with 36 upvotes was promoting a global BlocProvider pattern that could lead to serious performance issues and memory leaks.
To provide clear, practical guidance on proper bloc implementation, backed by insights from the bloc library creator himself.
Help developers avoid common pitfalls and build more maintainable, performant Flutter applications.
Before diving in, you should have:
Consider a common Flutter application structure with authentication flow:
App() => LoginPage() => HomePage() => UserTokensPage()
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: BlocProvider(
create: (context) => UserBloc(UserRepository()),
child: LoginPage(),
),
);
}
}
A highly upvoted answer suggested wrapping the entire app with MultiBlocProvider:
void main() {
runApp(
MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => UserBloc(UserRepository()),
),
// More global providers...
],
child: App()
)
);
}
Limit bloc access to only the widgets that need it
Ensure proper disposal of blocs when not needed
Maintain clear boundaries between features
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LoginBloc(
authRepository: context.read(),
),
child: LoginView(),
);
}
}
class LoginView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener(
listener: (context, state) {
if (state.status == LoginStatus.success) {
// Navigate and remove login route from stack
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => HomePage()),
);
// LoginBloc will be automatically disposed
}
},
child: // Your login form widgets
);
}
}
pushReplacement
instead of push
to remove the login page from navigation stackBlocProvider.value
only when passing existing bloc instances// Example: Dashboard page requiring multiple related features
class DashboardPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => UserStatsBloc()..add(LoadUserStats()),
),
BlocProvider(
create: (context) => NotificationsBloc()..add(LoadNotifications()),
),
BlocProvider(
create: (context) => ActivityBloc()..add(LoadRecentActivity()),
),
],
child: DashboardView(),
);
}
}
When multiple blocs are needed for a specific feature's functionality
DashboardPage with related stats, notifications, and activity blocs
When blocs have interdependent functionality
Shopping cart with inventory and payment blocs
When multiple blocs share the same lifecycle
User profile with settings and preferences blocs
Don't wrap MaterialApp with unrelated blocs
Wrapping entire app with authentication, settings, and cart blocs
Don't combine blocs that serve different purposes
Mixing login bloc with product catalog bloc
Don't combine blocs with different disposal needs
Combining temporary chat bloc with persistent theme bloc
// E-commerce product detail page example
class ProductDetailPage extends StatelessWidget {
final String productId;
ProductDetailPage({required this.productId});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
// Related blocs that work together
BlocProvider(
create: (context) => ProductDetailBloc()
..add(LoadProductDetails(productId)),
),
BlocProvider(
create: (context) => ProductReviewsBloc()
..add(LoadProductReviews(productId)),
),
BlocProvider(
create: (context) => RelatedProductsBloc()
..add(LoadRelatedProducts(productId)),
),
],
child: ProductDetailView(),
);
}
}
// These blocs will be disposed when leaving ProductDetailPage