How to Use Provider for State Management in Flutter || Part - 2

In Flutter, managing state efficiently is crucial for building responsive and dynamic applications. In the first part of this series, we explored the basics of using the Provider package for state management. In this second part, we will delve deeper into FutureProvider and StreamProvider, two powerful tools that help handle asynchronous data in Flutter apps. By understanding how to use these providers, you can ensure your app remains responsive and provides a seamless user experience. If you missed the first part, you can check it out here.

What is FutureProvider?

In Flutter, FutureProvider is a part of the Provider package, which helps manage state in your app. Let me explain what it does:

  1. Purpose ofFutureProvider:

    • FutureProvider is used to provide a value that might not be ready when the widget tree is built. It ensures that a null value isn’t passed to any widgets.

    • The main use-case is to handle asynchronous operations (like fetching data from an API) and provide the result to widgets once the future is completed.

  2. How it works:

    • You wrap your widget tree with a FutureProvider.

    • The FutureProvider takes a Future class (e.g., an API call) and updates the widgets depending on it when the future completes.

    • Until the future resolves, you can provide an initial value (e.g., a loading spinner) to avoid null errors.

  3. Example:

     FutureProvider<String>(
       create: (_) => fetchDataFromApi(), // Your async operation
       initialData: 'Loading...', // Initial value
       child: MyWidget(),
     )
    

Remember, FutureProvider is particularly useful when dealing with asynchronous data in your Flutter app!


How to use FutureProvider in a Flutter app?

In Flutter, FutureProvider from the Provider package is used to handle asynchronous operations and provide the result to widgets once the future completes. Here’s how you can use it:

  1. Exposing an Immutable Value:

    • If you have an “immutable value” that needs to be asynchronously loaded (e.g., a config file), use FutureProvider like this:

        FutureProvider<MyConfig>(
          builder: (_) async {
            final json = await // Load JSON from somewhere (e.g., an API);
            return MyConfig.fromJson(json);
          },
        )
      
  2. Combining with Mutations:

    • To convert a Future into something easier to manipulate, combine FutureProvider with a mutable object (like ChangeNotifier):

        ChangeNotifierProvider(
          builder: (_) => Foo(),
          child: Consumer<Foo>(
            builder: (_, foo, __) {
              return FutureProvider.value(
                value: foo.someFuture,
                child: ... // Your widget tree
              );
            },
          ),
        )
      
    • This allows you to handle updates when the future completes.

Remember, FutureProvider is particularly useful for managing asynchronous data in your Flutter app!


What is StreamProvider?

In Flutter, StreamProvider is a state management solution that leverages the power of the Dart Stream API and the simplicity of Flutter’s Provider package. Here’s what you need to know:

  1. Purpose of StreamProvider:

    • Exposing a Stream: StreamProvider is used to expose a stream and allows its descendants to access the latest value emitted by that stream.

    • Listening to Changes: It listens to the stream and automatically updates the widgets when new data arrives.

  2. How to Use StreamProvider:

    • Wrap your widget tree with StreamProvider:

        StreamProvider<int>(
          create: (_) => myStream, // Your stream (e.g., from Firestore, WebSocket, etc.)
          initialData: 0, // Initial value
          child: MyWidget(),
        )
      
    • Replace int with the type of data your stream emits (e.g., String, List<User>, etc.).

  3. Difference Between StreamProvider and StreamBuilder:

    • StreamBuilder: Rebuilds itself every time the stream updates. It’s a built-in Flutter widget.

    • StreamProvider: Combines StreamBuilder with InheritedWidget, allowing efficient data passing through the widget tree.


How to use StreamProvider in a Flutter app?

In your Flutter app, StreamProvider is a powerful way to handle asynchronous data using streams. Let’s dive into how you can use it:

  1. Import the Necessary Packages: First, make sure you have the provider package added to your pubspec.yaml file:

     dependencies:
       flutter:
         sdk: flutter
       provider: ^6.1.2
    
  2. Create a StreamProvider: Wrap your widget tree with StreamProvider. Specify the type of data your stream emits (e.g., Stream<int> or Stream<List<User>>):

     StreamProvider<int>(
       create: (_) => myStream, // Your stream (e.g., from Firestore, WebSocket, etc.)
       initialData: 0, // Initial value
       child: MyWidget(),
     )
    
  3. Access the Stream in Your Widgets: Inside MyWidget or any descendant widget, use context.watch or context.read to access the stream:

     final myValue = context.watch<int>(); // Access the stream value
    
  4. Update UI When Stream Emits Data: Whenever your stream emits new data, the widgets wrapped by StreamProvider will automatically rebuild with the updated value.

Remember, StreamProvider simplifies handling asynchronous data in your app!


What is ValueListenableBuilder in flutter?

Certainly! The ValueListenableBuilder in Flutter is a handy widget that keeps its content in sync with a ValueListenable. Here’s how it works:

  1. What isValueListenableBuilder?

    • ValueListenableBuilder is a widget that listens to changes in a ValueListenable and automatically rebuilds its content when the value changes.

    • You provide a ValueListenable<T> and a builder function that creates widgets based on the current value of the ValueListenable.

    • When the value changes, the builder function is called with the updated value, allowing you to update your UI accordingly.

  2. Usage Example:

     ValueListenableBuilder<int>(
       valueListenable: myValueListenable, // Your ValueListenable instance
       builder: (BuildContext context, int value, Widget? child) {
         // Build your UI based on the current value
         return Text('Count: $value');
       },
     )
    
  3. Performance Optimisation:

    • If your builder function contains a subtree that doesn’t depend on the ValueListenable value, you can pass a pre-built subtree as the child parameter.

    • This pre-built child is optional but can significantly improve performance in some cases.

  4. Other Similar Widgets:

    • AnimatedBuilder: Rebuilds based on a Listenable without passing back a specific value.

    • NotificationListener: Rebuilds based on notifications from descendant widgets.

    • StreamBuilder: For more advanced use cases involving streams.

    • TweenAnimationBuilder: Animates values based on a Tween.

Remember to replace myValueListenable with your actual ValueListenable instance.


How to create your own ValueNotifier in Flutter?

Certainly! Creating your own ValueNotifier in Flutter is straightforward. It’s a simple class that extends ChangeNotifier and provides a way to store a single value that can be listened to and updated reactively. Let’s walk through an example:

import 'package:flutter/material.dart';

class CounterModel extends ValueNotifier<int> {
  CounterModel(int value) : super(value);

  void increment() => value++;
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = CounterModel(0);

    return MaterialApp(
      title: 'ValueNotifier Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('ValueNotifier Demo'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('You have pushed the button this many times:'),
              ValueListenableBuilder(
                valueListenable: counter,
                builder: (BuildContext context, int value, Widget? child) {
                  return Text(
                    '$value',
                    style: Theme.of(context).textTheme.headline4,
                  );
                },
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            counter.increment();
          },
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

In this example:

  1. We define a CounterModel class that extends ValueNotifier<int>.

  2. The increment() method updates the counter value.

  3. We create an instance of CounterModel and pass it to ValueListenableBuilder, which listens to changes in the counter value and rebuilds the widget tree when the value changes.

Using ValueNotifier is that simple! It's a great choice for small to medium-sized projects where you don't need the complexity of other state management solution. keep in mind that for larger or more complex projects, other state management solutions like Riverpod, Provider, Bloc, or Redux might be more suitable.


In this article, we explored FutureProvider and StreamProvider in Flutter. These tools help manage asynchronous data, ensuring your app remains responsive and dynamic. By using these providers, you can efficiently handle state management in your Flutter applications.

10
Subscribe to my newsletter

Read articles from Fenisha Koladiya directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Fenisha Koladiya
Fenisha Koladiya