Communication between UI and Provider
Last modified on Tue 29 Jun 2021

Events (UI -> Provider)

First type of communication is when UI has to run some method on Provider. We're calling these events just like Bloc and some other state management solutions would.

In provider they are simple methods. For example:

RaisedButton(
  child: Text('Save'),
  onPressed: () {
    provider.onSavePressed();
  }
)

Take a note on a naming. The events don't command on provider, but more say onSomethingHappened and provider will decide what to do. Wrong naming would be provider.saveProfile().

View state (UI <- Provider)

Second type is when provider is managing view state. The UI should observe that state and rebuild. Usually this is done by our providers extending ChangeNotifier or StateNotifier:

    Consumer<MeetupScreenProvider>(
      builder: (context, provider, _) {
        return _MeetupList(list: provider.state);
      },
    ),

With riverpod you can use hooks to flatten this: https://riverpod.dev/docs/concepts/reading/#useprovider-hooks_riverpod-only

Actions (UI <- Provider)

The view state should describe how the view tree should be built. Sometimes provider needs to communicate other things that don't fall under view tree of that widget. Most often these are:

For these we don't use View state, but create separate action subjects which UI can listen:

class ProfileScreenProvider extends ChangeNotifier {
  final PublishSubject<Profile> _navigateToProfileDetailsSubject = PublishSubject();
  Stream<Profile> get navigateToProfileDetails => _navigateToProfileDetailsSubject;

  // ... view state

  void onProfileClicked() {
    if (profile.hasDetails()) {
      _navigateToProfileDetailsSubject.add(profile);
    }
  }

On the UI side you need listen the stream. You can use DisposableListener widget so you don't have to use StatefulWidget just for the listening:

onListen: (compositeSubscription) {
  provider.navigateToProfileDetails.listen((profile) {
    Navigator.of(context).push(ProfileDetailsScreen.route(profile));
  }).addTo(compositeSubscription)
}

Returning result back to provider

Sometime with the navigation you want to return result of navigation or dialog back to the provider. With previously explained setup it's possible to do that with Completer.

class ProfileScreenProvider extends ChangeNotifier {
  final PublishSubject<Completer<Profile>> _showChooseProfileDialogSubject = PublishSubject();
  Stream<NavigateToPermissionData> get showChooseProfileDialog => _showChooseProfileDialogSubject;

  // ... view state

  void onSetFavoriteProfileClicked() {
    final completer = Completer<Profile>();
    _showChooseProfileDialogSubject.add(completer));

    // Do something with the result
    final result = await completer.future;
    _saveFavoriteProfile(result);
  }

On the UI side using DisposableListener:

onListen: (compositeSubscription) {
  provider.showChooseProfileDialog.listen((completer) {
     final result = _showChooseProfileDialog(context);
     completer.complete(result);
  }).addTo(compositeSubscription)
}