Arisprovider - A mixture between dependency injection (DI) and state management, built with widgets for widgets

Overview

Build Status pub package codecov Gitter

A mixture between dependency injection (DI) and state management, built with widgets for widgets.

It purposefully uses widgets for DI/state management instead of dart-only classes like Stream. The reason is, widgets are very simple yet robust and scalable.

By using widgets for state management, provider can guarantee:

  • maintainability, through a forced uni-directional data-flow
  • testability/composability, since it is always possible to mock/override a value
  • robustness, as it is harder to forget to handle the update scenario of a model/widget

To read more about provider, see the documentation.

Migration from v2.0.0 to v3.0.0

  • Providers can no longer be instantiated with const.
  • Provider now throws if used with a Listenable/Stream. Consider using ListenableProvider/StreamProvider instead. Alternatively, this exception can be disabled by setting Provider.debugCheckInvalidValueType to null like so:
void main() {
  Provider.debugCheckInvalidValueType = null;

  runApp(MyApp());
}
  • All XXProvider.value constructors now use value as parameter name.

Before:

ChangeNotifierProvider.value(notifier: myNotifier),

After:

ChangeNotifierProvider.value(value: myNotifier),
  • StreamProvider's default constructor now builds a Stream instead of a StreamController. The previous behavior has been moved to the named constructor StreamProvider.controller.

Before:

StreamProvider(builder: (_) => StreamController<int>()),

After:

StreamProvider.controller(builder: (_) => StreamController<int>()),

Usage

Exposing a value

To expose a variable using provider, wrap any widget into one of the provider widgets from this package and pass it your variable. Then, all descendants of the newly added provider widget can access this variable.

A simple example would be to wrap the entire application into a Provider widget and pass it our variable:

Provider<String>.value(
  value: 'Hello World',
  child: MaterialApp(
    home: Home(),
  )
)

Alternatively, for complex objects, most providers expose a constructor that takes a function to create the value. The provider will call that function only once, when inserting the widget in the tree, and expose the result. This is perfect for exposing a complex object that never changes over time without writing a StatefulWidget.

The following creates and exposes a MyComplexClass. And in the event where Provider is removed from the widget tree, the instantiated MyComplexClass will be disposed.

Provider<MyComplexClass>(
  builder: (context) => MyComplexClass(),
  dispose: (context, value) => value.dispose()
  child: SomeWidget(),
)

Reading a value

The easiest way to read a value is by using the static method Provider.of<T>(BuildContext context).

This method will look up in the widget tree starting from the widget associated with the BuildContext passed and it will return the nearest variable of type T found (or throw if nothing is found).

Combined with the first example of exposing a value, this widget will read the exposed String and render "Hello World."

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      /// Don't forget to pass the type of the object you want to obtain to `Provider.of`!
      Provider.of<String>(context)
    );
  }
}

Alternatively instead of using Provider.of, we can use Consumer and Selector.

These can be useful for performance optimizations or when it is difficult to obtain a BuildContext descendant of the provider.

See the FAQ or the documentation of Consumer and Selector for more information.

MultiProvider

When injecting many values in big applications, Provider can rapidly become pretty nested:

Provider<Foo>.value(
  value: foo,
  child: Provider<Bar>.value(
    value: bar,
    child: Provider<Baz>.value(
      value: baz,
      child: someWidget,
    )
  )
)

In that situation, we can use MultiProvider to improve the readability:

MultiProvider(
  providers: [
    Provider<Foo>.value(value: foo),
    Provider<Bar>.value(value: bar),
    Provider<Baz>.value(value: baz),
  ],
  child: someWidget,
)

The behavior of both examples is strictly the same. MultiProvider only changes the appearance of the code.

ProxyProvider

Since the 3.0.0, there is a new kind of provider: ProxyProvider.

ProxyProvider is a provider that combines multiple values from other providers into a new object, and sends the result to Provider.

That new object will then be updated whenever one of the providers it depends on updates.

The following example uses ProxyProvider to build translations based on a counter coming from another provider.

Widget build(BuildContext context) {
  return MultiProvider(
    providers: [
      ChangeNotifierProvider(builder: (_) => Counter()),
      ProxyProvider<Counter, Translations>(
        builder: (_, counter, __) => Translations(counter.value),
      ),
    ],
    child: Foo(),
  );
}

class Translations {
  const Translations(this._value);

  final int _value;

  String get title => 'You clicked $_value times';
}

It comes under multiple variations, such as:

  • ProxyProvider vs ProxyProvider2 vs ProxyProvider3, ...

    That digit after the class name is the number of other providers that ProxyProvider depends on.

  • ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider, ...

    They all work similarly, but instead of sending the result into a Provider, a ChangeNotifierProxyProvider will send its value to a ChangeNotifierProvider.

FAQ

I have an exception when obtaining Providers inside initState. What can I do?

This exception happens because you're trying to listen to a provider from a life-cycle that will never ever be called again.

It means that you either should use another life-cycle (didChangeDependencies/build), or explicitly specify that you do not care about updates.

As such, instead of:

initState() {
  super.initState();
  print(Provider.of<Foo>(context).value);
}

you can do:

Value value;

didChangeDependencies() {
  super.didChangeDependencies();
  final value = Provider.of<Foo>(context).value;
  if (value != this.value) {
    this.value = value;
    print(value);
  }
}

which will print value whenever it changes.

Alternatively you can do:

initState() {
  super.initState();
  print(Provider.of<Foo>(context, listen: false).value);
}

Which will print value once and ignore updates.

I use ChangeNotifier and I have an exception when I update it, what happens?

This likely happens because you are modifying the ChangeNotifier from one of its descendants while the widget tree is building.

A typical situation where this happens is when starting an http request, where the future is stored inside the notifier:

initState() {
  super.initState();
  Provider.of<Foo>(context).fetchSomething();
}

This is not allowed, because the modification is immediate.

Which means that some widgets may build before the mutation, while other widgets will build after the mutation. This could cause inconsistencies in your UI and is therefore not allowed.

Instead, you should perform that mutation in a place that would affect the entire tree equally:

  • directly inside the builder of your provider/constructor of your model:

    class MyNotifier with ChangeNotifier {
      MyNotifier() {
        _fetchSomething();
      }
    
      Future<void> _fetchSomething() async {}
    }

    This is useful when there's no "external parameter".

  • asynchronously at the end of the frame:

    initState() {
      super.initState();
      Future.microtask(() =>
        Provider.of<Foo>(context).fetchSomething(someValue);
      );
    }

    It is slightly less ideal, but allows passing parameters to the mutation.

Do I have to use ChangeNotifier for complex states?

No.

You can use any object to represent your state. For example, an alternate architecture is to use Provider.value() combined with a StatefulWidget.

Here's a counter example using such architecture:

class Example extends StatefulWidget {
  const Example({Key key, this.child}) : super(key: key);

  final Widget child;

  @override
  ExampleState createState() => ExampleState();
}

class ExampleState extends State<Example> {
  int _count;

  void increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Provider.value(
      value: _count,
      child: Provider.value(
        value: this,
        child: widget.child,
      ),
    );
  }
}

where we can read the state by doing:

return Text(Provider.of<int>(context).toString());

and modify the state with:

return FloatingActionButton(
  onPressed: Provider.of<ExampleState>(context).increment,
  child: Icon(Icons.plus_one),
);

Alternatively, you can create your own provider.

Can I make my own Provider?

Yes. provider exposes all the small components that makes a fully fledged provider.

This includes:

  • SingleChildCloneableWidget, to make any widget works with MultiProvider.
  • InheritedProvider, the generic InheritedWidget obtained when doing Provider.of.
  • DelegateWidget/BuilderDelegate/ValueDelegate to help handle the logic of "MyProvider() that creates an object" vs "MyProvider.value() that can update over time".

Here's an example of a custom provider to use ValueNotifier as state: https://gist.github.com/rrousselGit/4910f3125e41600df3c2577e26967c91

My widget rebuilds too often, what can I do?

Instead of Provider.of, you can use Consumer/Selector.

Their optional child argument allows to rebuild only a very specific part of the widget tree:

Foo(
  child: Consumer<A>(
    builder: (_, a, child) {
      return Bar(a: a, child: child);
    },
    child: Baz(),
  ),
)

In this example, only Bar will rebuild when A updates. Foo and Baz won't unnecesseraly rebuild.

To go one step further, it is possible to use Selector to ignore changes if they don't have an impact on the widget-tree:

Selector<List, int>(
  selector: (_, list) => list.length,
  builder: (_, length, __) {
    return Text('$length');
  }
);

This snippet will rebuild only if the length of the list changes. But it won't unnecessarily update if an item is updated.

Can I obtain two different providers using the same type?

No. While you can have multiple providers sharing the same type, a widget will be able to obtain only one of them: the closest ancestor.

Instead, you must explicitly give both providers a different type.

Instead of:

Provider<String>(
  builder: (_) => 'England',
  child: Provider<String>(
    builder: (_) => 'London',
    child: ...,
  ),
),

Prefer:

Provider<Country>(
  builder: (_) => Country('England'),
  child: Provider<City>(
    builder: (_) => City('London'),
    child: ...,
  ),
),

Existing providers

provider exposes a few different kinds of "provider" for different types of objects.

The complete list of all the objects availables is here

name description
Provider The most basic form of provider. It takes a value and exposes it, whatever the value is.
ListenableProvider A specific provider for Listenable object. ListenableProvider will listen to the object and ask widgets which depend on it to rebuild whenever the listener is called.
ChangeNotifierProvider A specification of ListenableProvider for ChangeNotifier. It will automatically call ChangeNotifier.dispose when needed.
ValueListenableProvider Listen to a ValueListenable and only expose ValueListenable.value.
StreamProvider Listen to a Stream and expose the latest value emitted.
FutureProvider Takes a Future and updates dependents when the future completes.
You might also like...

A powerful state machine for MobX management, that can be used in almost any application state.

A powerful state machine for MobX management, which can be used in almost any application state. It has 3 states - loading, success, error - and is pe

Oct 31, 2022

Practice building basic animations in apps along with managing app state by BLoC State Management, Flutter Slider.

Practice building basic animations in apps along with managing app state by BLoC State Management including: Cubit & Animation Widget, Flutter Slider.

Jun 8, 2022

Riverpod State Mgmt for Flutter. StateProviders, StateNotifierProviders, FutureProviders, StreamProviders, autodisposed and families, and everything in-between.

Riverpod State Mgmt for Flutter. StateProviders, StateNotifierProviders, FutureProviders, StreamProviders, autodisposed and families, and everything in-between.

Flutter Riverpod Learning/Reference zone A Null-Safety flutter project acting as a learning/code reference zone Based on Riverpod (v1/v2) Page Transit

Dec 24, 2022

An all-in-one Fllutter package for state management, reactive objects, animations, effects, timed widgets etc.

An all-in-one Fllutter package for state management, reactive objects, animations, effects, timed widgets etc.

Frideos An all-in-one package for state management, streams and BLoC pattern, animations and timed widgets, effects. Contents 1. State management Gett

Dec 23, 2022

A Flutter application that allows interacting with Pokemons, built using Clean Archtitecture structure and riverpod as state management.

A Flutter application that allows interacting with Pokemons, built using Clean Archtitecture structure and riverpod as state management.

pokedex Welcome to the Pokedex project! Getting Started Instructions to run: This project uses flutter version 3.0.3, Make sure you have that version

Nov 22, 2022

A project built to learn Flutter state management

Tienda Tienda is a simple product store mobile application. Tienda is a flutter application which was solely built to learn App state and local state

Nov 28, 2021

A simple to-do list built using flutter based on BLoC state management to manage your daily tasks .

A simple to-do list built using flutter based on BLoC state management to manage your daily tasks .

📝 Table of Contents About ScreenShots from the app Demo vedio Contributors About A to-do list flutter app to manage your daily tasks. it is built bas

Oct 12, 2022

A zero-dependency web framework for writing web apps in plain Dart.

Rad Rad is a frontend framework for creating fast and interactive web apps using Dart. It's inspired from Flutter and shares same programming paradigm

Dec 13, 2022
Owner
Behruz Hurramov
Behruz Hurramov
Behruz Hurramov
MVC pattern for flutter. Works as state management, dependency injection and service locator.

MVC pattern for flutter. Works as state management, dependency injection and service locator. Model View Controller Here's a diagram describing the fl

xamantra 115 Dec 12, 2022
Flutter getx template - A Flutter Template using GetX package for State management, routing and Dependency Injection

Flutter GetX Template (GetX, Dio, MVVM) This Flutter Template using GetX package

Tareq Islam 6 Aug 27, 2022
[Flutter SDK V.2] - Youtube Video is a Flutter application built to demonstrate the use of Modern development tools with best practices implementation like Clean Architecture, Modularization, Dependency Injection, BLoC, etc.

[Flutter SDK V.2] - Youtube Video is a Flutter application built to demonstrate the use of Modern development tools with best practices implementation like Clean Architecture, Modularization, Dependency Injection, BLoC, etc.

R. Rifa Fauzi Komara 17 Jan 2, 2023
Raden Saleh 20 Aug 12, 2023
A simple dependency injection plugin for Flutter and Dart.

A super simple dependency injection implementation for flutter that behaviours like any normal IOC container and does not rely on mirrors

Jon Samwell 91 Dec 13, 2022
A Dart dependency injection library aimed to be flexible, predictable and easy to use.

dino Dino is a Dart dependency injection library with optional code generation. It was inspired by DI in .NET and aimed to be flexible, predictable an

null 3 Dec 20, 2022
Clean Architecture + TDD + SOLID + Dependency Injection + GitFlow + Mobx

Clean Architecture + TDD + SOLID + Dependency Injection + GitFlow + Mobx Flutter Interview Challenge This app is part of an interview process. It took

Vinicius Souza 13 Dec 28, 2022
State Persistence - Persist state across app launches. By default this library store state as a local JSON file called `data.json` in the applications data directory. Maintainer: @slightfoot

State Persistence Persist state across app launches. By default this library store state as a local JSON file called data.json in the applications dat

Flutter Community 70 Sep 28, 2022
Shopify Tag and Product Management App using Flutter and Riverpod State Management

Myshopify App A Simple Flutter Application project to get List of Tags, Products and Product Details from shopify https://shopicruit.myshopify.com/adm

Idowu Tomiwa 5 Nov 12, 2022
An extension to the bloc state management library which lets you create State Machine using a declarative API

An extension to the bloc state management library which lets you create State Machine using a declarative API

null 25 Nov 28, 2022