In this article, you can explore the fundamentals of using Provider in Flutter to manage the state efficiently. This guide covers essential concepts, from creating and defining providers to accessing and updating data within your Flutter applications. It is ideal for developers looking to simplify state management in their basic Flutter projects.

Create Provider

Data changes over time.

  • Use with ChangeNotifier;
  • Call notifyListeners() once the data changes to get updates on the UI;
class ProductProvider with ChangeNotifier {
  final Future<List<Product>> _product;
  final List<String> _someData;

  ProductProvider() {
  }

  void doSomeLogic() {
    ...
    _someData = newData;
    notifyListeners();
  }

  void doSomeLogicAgain() {
    ...
    _product = newProduct;
    notifyListeners();
  }

Provider data doesn’t change over time.

class ProductProvider {
  final Future<List<Product>> _product;
  final List<String> _someData;

  ProductProvider() {
  }

  void doSomeLogic() {
    ...
    _someData = newData;
  }

  void doSomeLogicAgain() {
    ...
    _product = newProduct;
  }
}

Define Providers in the Application

  • Use MultiProvider to define all your Provider classes
  • Provider sends updates to the UI => use ChangeNotifierProvider
  • Provider has immutable data and doesn’t update => Provider
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) => MultiProvider(
        providers: [
          Provider<ProductRepository>(
            create: (BuildContext context) => ProductRepository(),
          ),
          ChangeNotifierProvider<ProductProvider>(
            create: (BuildContext context) => ProductProvider(),
          )
        ],
        child: Theme.of(context).platform == TargetPlatform.android
            ? const MaterialDesignApp()
            : const CupertinoDesignApp(),
      );
}

Access Data from the Provider

Access data, one-time reads, method calls

  • Provider.of<YorProvider>(context, listen: false).doSomething();
  • context.read<YourProvider>().yourData;
floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<Counter>().increment(),
        child: Icon(Icons.add),
      ),
floatingActionButton: FloatingActionButton(
        onPressed: () => Provider.of<Counter>(context, listen:false).increment(),
        child: Icon(Icons.add),
      ),

Listening for the updates from Provider, data changes over time

API calls:

Widgets:

  • Consumer
  • Selector
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Count: ${context.watch<Counter>().count}');
  }
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Count: ${Provider.of<Counter>(context, listen: true).count}');
  }
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<Counter>(
      builder: (context, counter, child) {
        return Text('Count: ${counter.count}');
      },
    );
  }
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Selector<MyProvider, int>(
      selector: (BuildContext context, MyProvider myProvider) => myProvider.count
      builder: (context, counter, child) {
        return Text('Count: $count');
      },
    );
  }
}

class MyProvider with ChangeNotifier {
    final int count;
    final String dataString;
    final List<Product> product;
    final String moreData;
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final count = context.select((MyProvider p) => p.count);
    return Text('Count: $count');
  }
}

class MyProvider with ChangeNotifier {
    final int count;
    final String dataString;
    final List<Product> product;
    final String moreData;

    MyProvider(this.count, this.dataString, this.product, this.moreData);
}

Best Practices

  1. Use Multiple Providers for Complex State. For complex apps, split your state management into multiple smaller providers rather than having a single, monolithic provider. This improves maintainability and performance.
  2. Avoid Unnecessary Rebuilds. Use the select method or Selector widget to listen to specific parts of the model that need to be rebuilt, avoiding unnecessary widget rebuilds.
  3. Use Read and Watch Appropriately. Use context.read<T>() to access a provider’s instance without listening to changes. Use context.watch<T>() when you need to rebuild your widget when the provider data changes.
  4. Dispose of Providers Correctly. When using Provider in a widget that needs to be disposed of, ensure you properly clean up resources by using the dispose method.


Discover more from FutureWare

Subscribe to get the latest posts sent to your email.

Discover more from FutureWare

Subscribe now to keep reading and get access to the full archive.

Continue reading