top of page

State Management in Flutter: Provider vs. BLoC vs. Redux


State Management in Flutter: Provider vs. BLoC vs. Redux

State management is one of the most important concepts in Flutter app development. Managing state effectively can make your app more efficient, faster, and easier to maintain.


In this article, we'll explore three popular state management solutions in Flutter: Provider, BLoC, and Redux.


State Management in Flutter


In Flutter, state management refers to the way in which data is managed and updated within an app. In general, there are two types of state: local state and global state.


Local state is data that is used only within a single widget. For example, if you have a button that changes color when clicked, the color of the button is a piece of local state.


Global state is data that needs to be accessed by multiple widgets within the app. For example, if you have a shopping app and you need to keep track of the user's cart across multiple screens, the contents of the cart are global state.


1. Provider

Provider is a state management solution that was introduced as an alternative to Flutter's built-in setState() method. Provider is a relatively new solution but has gained popularity among developers because of its simplicity and ease of use.


Provider works by creating a central data store that can be accessed by any widget within the app. This data store is known as a ChangeNotifier and is responsible for managing the app's global state.


Here is an example of how to use Provider in Flutter:

class CartModel extends ChangeNotifier {
  List<Item> _items = [];

  List<Item> get items => _items;

  void addItem(Item item) {
    _items.add(item);
    notifyListeners();
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => CartModel(),
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final cart = Provider.of<CartModel>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text('My App'),
      ),
      body: Center(
        child: FlatButton(
          onPressed: () {
            cart.addItem(Item(name: 'Item 1', price: 10));
          },
          child: Text('Add to Cart'),
        ),
      ),
    );
  }
}

In this example, we create a CartModel class that manages the app's global state. We then wrap our MyApp widget in a ChangeNotifierProvider, which provides access to the CartModel to any widget within the app. Finally, in the MyHomePage widget, we use the Provider.of<CartModel>(context) method to access the CartModel and add items to the cart when the user clicks the "Add to Cart" button.


2. BLoC

BLoC (Business Logic Component) is another popular state management solution in Flutter. BLoC separates the business logic of the app from the user interface, making it easier to manage complex state.


BLoC works by creating a stream of data that emits events whenever the state changes. Widgets can then subscribe to this stream and update themselves accordingly.


Here is an example of how to use BLoC in Flutter:

class CartBloc {
  final _cart = BehaviorSubject<List<Item>>.seeded([]);

  Stream<List<Item>> get cart => _cart.stream;

  void addItem(Item item) {
    final items = _cart.value;
    items.add(item);
    _cart.add(items);
  }

  void dispose() {
    _cart.close();
  }
}

class MyApp extends StatelessWidget {
  final cart = CartBloc();

  @override 
  Widget build(BuildContext context) { 
    return StreamProvider<List<Item>>.value(
      value: cart.cart,
      initialData: [],
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }

  @override 
  void dispose() { 
    cart.dispose();
    super.dispose();
  }
}

class MyHomePage extends StatelessWidget {
  @override 
  Widget build(BuildContext context) { 
    final cart = Provider.of<List<Item>>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('My App'),
      ),
      body: Center(
        child: FlatButton(
          onPressed: () {
            Provider.of<CartBloc>(context, listen: false).addItem(Item(name: 'Item 1', price: 10));
          },
          child: Text('Add to Cart'),
        ),
      ),
    );
  }
}

In this example, we create a `CartBloc` class that manages the app's global state. We then use a `StreamProvider` to provide access to the `cart` stream to any widget within the app. Finally, in the `MyHomePage` widget, we use the `Provider.of<List<Item>>(context)` method to access the cart and add items to the cart when the user clicks the "Add to Cart" button.


3. Redux


Redux is a popular state management solution in the web development world, and has also gained popularity in the Flutter community. Redux works by creating a single data store that is responsible for managing the app's global state. This data store is modified by dispatching actions, which are then handled by reducers that update the state.


Here is an example of how to use Redux in Flutter:

enum CartAction { addItem }

class CartState {
  final List<Item> items;

  CartState({this.items});

  CartState.initialState() : items = [];
}

CartState cartReducer(CartState state, dynamic action) {
  if (action == CartAction.addItem) {
    return CartState(items: List.from(state.items)..add(Item(name: 'Item 1', price: 10)));
  }

  return state;
}

class MyApp extends StatelessWidget {
  final store = Store<CartState>(cartReducer, initialState: CartState.initialState());

  @overrideWidget build(BuildContext context) {
    return StoreProvider(
      store: store,
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('My App'),
      ),
      body: Center(
        child: FlatButton(
          onPressed: () {
            StoreProvider.of<CartState>(context).dispatch(CartAction.addItem);
          },
          child: Text('Add to Cart'),
        ),
      ),
    );
  }
}

In this example, we create a CartState class that manages the app's global state. We then use a StoreProvider to provide access to the store to any widget within the app. Finally, in the MyHomePage widget, we use the StoreProvider.of<CartState>(context) method to access the store and dispatch an action to add an item to the cart when the user clicks the "Add to Cart" button.


Conclusion


There are several popular state management solutions in Flutter, including Provider, BLoC, and Redux. Each solution has its own strengths and weaknesses, and the best solution for your project will depend on a variety of factors, including the complexity of the app and the preferences of the development team.


When choosing a state management solution, it's important to consider factors such as the ease of use, the level of abstraction, the performance, and the scalability of the solution. It's also important to consider the trade-offs between different solutions in terms of code complexity, maintenance, and the ability to integrate with other tools and libraries.


Provider is a great choice for simple apps with straightforward state management needs. It is easy to use and has a low learning curve, making it a popular choice for beginners.


BLoC is a more complex solution that offers a high level of abstraction, making it a good choice for complex apps with complex state management needs.


Redux is a mature and battle-tested solution that is widely used in the web development world and offers excellent scalability and performance.


The best state management solution for your project will depend on a variety of factors, including the size and complexity of your app, your team's preferences and skill level, and your performance and scalability requirements.


Regardless of which solution you choose, it's important to follow best practices for state management, such as separating UI logic from business logic, minimizing unnecessary state changes, and keeping state management code as simple and modular as possible. With the right approach and the right tools, you can build robust and scalable Flutter apps that deliver great user experiences and meet your business goals.

Blog for Mobile App Developers, Testers and App Owners

 

This blog is from Finotes Team. Finotes is a lightweight mobile APM and bug detection tool for iOS and Android apps.

​

In this blog we talk about iOS and Android app development technologies, languages and frameworks like Java, Kotlin, Swift, Objective-C, Dart and Flutter that are used to build mobile apps. Read articles from Finotes team about good programming and software engineering practices, testing and QA practices, performance issues and bugs, concepts and techniques. 

bottom of page