Using Riverpod with Flutter

I’m new to app development. This is 2023 notes about state management in Flutter using Riverpod.

Code generation

Probably better NOT to use this at the start, it’s another layer of complexity. But it’s nice if I ever understand what’s happening.

Getting Started

OMG, it’s a bit confusing. Docs are ok and give example of use cases (nice one Remi). Docs are called “old” – maybe about to change. It’s worth get the “Official examples” to run and play with.

Making Providers

A Provider exposes a read-only value. It exposes a read() method.

A Notifier can notify widgets it has changed. The widgets can ref.watch() a Notifier and will only rebuild if required. The ‘state’ of the Notifier is templated. For example Notifier<String> manages a String.

A NotifierProvider is both and an important class! It automatically and transparently caches values and allows them to be changed. You need to write a Notifier<your type> (eg Notifier<List<Todo>>) class, build() the initial value (e.g. an empty list), and methods modify the state of <your type>. Then you write a NotifierProvider like NotifierProvider <TodosNotifier, List<Todo>>(()) which takes the Notifier class and the <your type>. Note the repeated boiler plate (hence code generation).

A StateProvider is a special case of NotifierProvider that is designed only for basic types (int, String). It just avoid an explicit Notifer class like we had to make above.

FutureProvider: good for getting unchangable results from an API (via http.get => Future). If the result has to be user changed, use AsyncNotifierProvider instead. FutureProvider is a special case for simple uses. The widget displaying the (eg) API information can .watch the FutureProvider and use a helper class (ASyncResult) to display either “waiting” or “result” or “error” nicely.

ChangeNotifierProvider, StateNOtifierProvider : don’t use!

Jeez, that’s complex, show me examples

Yeah, IKR. Here are some example from the “Official Examples” github:

Marvel API

marvel.dart

final Provider<MarvelRepo> repoProvider = Provider(MarvelRepo.new)

Ok, we’re making a Provider to store an instance of type MarvelRepo. MarvelRepo is a class with no internal state, it just exposes “getInfoFromAPI” methods. (The “.new” bit I don’t understand. Just syntax thing. )

Then the rest of the file are just data-classes which take JSON from the API. The “getInfoFromAPI” methods return things like Future<Character> objects.

home.dart

We see a ConsumerWidget (recall ConsumerWidgets can access Providers via this.ref). In its build method it gets a Future<Character> (wrapped? in riverpod’s AsyncValue) and can easily display “loading” “error” “ready”. When in the “ready” state it returns Widgets displaying character.thumbnail.url, images, etc.

configration.dart

Makes a dataclass Configuration (with @freezed not frozen!) to store credentials.

Then creates a FutureProvider<Configuration> which returns just provides a (cached) Configuration instance from a static file. Note we don’t need to manually cache. We don’t need to modify so can avoid the more complex AsyncNotifierProvider.

final configurationsProvider = FutureProvider<Configuration>((_) async {
  final content = json.decode(
    await rootBundle.loadString('assets/configurations.json'),
  ) as Map<String, Object?>;

  return Configuration.fromJson(content);
});

rest of marvel example

There is more complexity in this example code due to deep linking, caching and pagination. Not covered – I don’t need that (yet!).

StackOverFlow Example

question.dart

Another data-class with @freezed called QuestionsResponse which stores a normal List of Question-s. Question dataclass stores a list of strings. This appears to be typical: the objects are modelled in data-classes. They are normal (not riverpod specific) and can be nested.

The mega-complex FutureProvider (below) is more complex due to paging. But I think its another FutureProvider wrapped in some autodisponse stuff. Note this Provider itself uses client which is a Provider<Dio>. I reckon this is so it’s using a single instance of Dio (an http client).

final paginatedQuestionsProvider = FutureProvider.autoDispose
    .family<QuestionsResponse, int>((ref, pageIndex) async {
    final response = await ref
      .watch(client)
      .getUri<Map<String, Object?>>(uri, cancelToken: cancelToken);
      ...more...
     returns QuestionResponse instance
}

Next we have a data-clas QuestionTheme and a Provider<QuestionsTheme>. This is interesting: it returns a const QuestionTheme which is unchanging. So it’s like a nice way of replacing a global variable???

… ignoring the rest … advanced

user.dart

Well freak me out, it’s another data-class User and Badge. It’s used in Question. Nothing riverpod-y. There’s also a custom Widget. It’s stateless: it gets final, immutable (it’s @freezed which defaults to immutable) object to display.

Leave a Reply

Your email address will not be published. Required fields are marked *