Search code examples
flutterriverpodflutter-hooks

Riverpode and Hooks in flutter, how to implement the StateNotifier for async calls?


I am new to flutter and recently I decided to look into providers to be able to reuse some sqlite DB calls I have using FutureBuilder as suggested in this other question.

After reading about it I started using riverpod_hooks but I got stuck.

Currently I have a provider to get the data from sqlite:

final bloodPressureProvider = FutureProvider((_) => _findAllBloodPressures());
Future<List<BloodPressure>> _findAllBloodPressures() async {
  var _bloodPressure = BloodPressure.forSearchOnly();
  var result = await _bloodPressure.findAll();
  return result;
}

Which I can successfully use to render a chart and display a list:

class BloodPressureList extends HookWidget {

  @override
  Widget build(BuildContext context) {
    final bpList = useProvider(bloodPressureProvider);
    return Scaffold(
      drawer: NutriDrawer(context).drawer(),
      appBar: AppBar(
        title: Text('Blood Pressure History'),
      ),
      body: Column(
        children: [
          Container(
            padding: EdgeInsets.fromLTRB(0, 25, 15, 5),
            child: bpList.when(
                data: (bloodPressureList) => _buildLineChart(bloodPressureList),
                loading: () => CircularProgressIndicator(),
                error: (_, __) => Text('Ooooopsss error'),
            ),
          ),
          Expanded(
            child: bpList.when(
              data: (bloodPressureList) => _getSlidableListView(bloodPressureList, context),
              loading: () => CircularProgressIndicator(),
              error: (_, __) => Text('Ooopsss error'),
            ),
          ),
        ],
      ), ...

The issue I currently have is to make the notification to the widgets whenever a new element is added to the DB which is done in another screen/widget, before using providers I would use setState but now I understand I should implement possibly a StateNotifier but I am missing the proper way to do it integrated with the asynchronous call returned from the DB used in the provider fragment above.

Any pointers in the right direction is greatly appreciated.

Update with solution: Based on the accepted answer below I used context.refresh and it did exactly what I was looking for:

var result = Navigator.push(context, MaterialPageRoute(builder: (context) {
      return BloodPressureDetail(title, bloodPressure);
    }));
    result.then((_) => context.refresh(bloodPressureProvider));

So when navigating back from the edit/add screen the provider context is refreshed and the state of the chart and list get updated automatically.


Solution

  • By default FutureProvider cache the result of the future so it's only fetched the first time it's accessed. If you want to get a new value each time you go to the screen use FutureProvider.autoDispose. If you want to manually refresh the provider you can use context.refresh(bloodPressureProvider) with FutureProvider.

    And if you don't want to access you db for fetching values but still want to have screens synchronized you can implement a stateNotifier which will represent your db state

    final dbState =
        StateNotifierProvider<dbState>((ProviderReference ref) {
      final dbState = DbState();
      dbState.init();
      return dbState;
    });
    
    class dbState extends StateNotifier<AsyncValue<List<String>>> {
       /// when the constructor is called the initial state is loading
       dbState() : super(AsyncLoading<List<String>>());
    
       /// fetch the values from the database and set the state to AsyncData 
       ///(could be good to add a try catch and set the sate to an error if one 
       /// occurs)
       void init()async{
           state = AsyncLoading<List<String>>();
           final fetchedValues = await fetchValues();
           state = AsyncData<List<String>>(fetchedValues);
       }
    
    }