riverpod icon indicating copy to clipboard operation
riverpod copied to clipboard

Add offline persistence support

Open rrousselGit opened this issue 2 years ago • 68 comments

Riverpod should allow simple integration with offline storage solutions

A possibility is to have Riverpod serialize/deserialize the current state of a provider and send that to a database, associated with a unique key corresponding to the provider.

rrousselGit avatar Dec 31 '21 12:12 rrousselGit

This sounds interesting. I might give this a try. Do you already have an idea in mind? If so can you share some classes I should look into to be able to implement a draft for this functionality?

JulianBissekkou avatar Jan 11 '22 06:01 JulianBissekkou

:wave: thanks for offering to help, but this specific issue is in a bit of a unique position. I'd like to personally take care of this one

rrousselGit avatar Jan 11 '22 15:01 rrousselGit

I don't know if it's gonna be bad, But I would say, you might want to take a look https://github.com/flutterdata/flutter_data or https://flutterdata.dev/docs/repositories/#architecture-overview deps

Finally, thank you for your library : ) (Translated above)

huang12zheng avatar Jan 23 '22 17:01 huang12zheng

This feature is not really related to what you're suggesting

What this issue involves will most likely be two bits:

  • new parameters on providers for decoding/encoding:

    final provider = SomeProvider(
      (ref) {},
      name: 'unique_key',
      decode: (data) => MyClass.fromJson(data),
      encode: (value) => value.toJson(),
    );
    
  • a way to tell ProviderScope/ProviderContainer how to obtain the encoded value of a provider

    ProviderScope(
      initialData: {
        'unique_key': { <some json> },
      },
      encoder: SomeDBEncoder(),
    ),
    

rrousselGit avatar Jan 29 '22 12:01 rrousselGit

this sounds very interesting, is this being added to the roadmap? @rrousselGit

ken-ng-esotec avatar Mar 05 '22 02:03 ken-ng-esotec

this sounds very interesting, is this being added to the roadmap? @rrousselGit

It is on the roadmap for https://github.com/rrousselGit/river_pod/milestone/1

chimon2000 avatar Mar 17 '22 16:03 chimon2000

You mentioned this issue in #1168 and while the scope of this feature doesn't seem as narrow as just restoring the state after the OS kills your app, I thought you might be interested in this lib I recently made that restores the state of providers: https://github.com/North101/flutter_riverpod_restorable

North101 avatar Mar 19 '22 23:03 North101

@North101 the repo link does not work, so I'll leave a link to your package:

https://pub.dev/packages/flutter_riverpod_restorable

dJani97 avatar Mar 20 '22 08:03 dJani97

I needed a solution for a generic riverpod persistence layer, and ended up with this:

https://github.com/tim-smart/riverpod_persistence/blob/main/test/riverpod_persistence_test.dart

Would be nice to have something official!

tim-smart avatar Apr 01 '22 07:04 tim-smart

Personally, I really like how states_rebuilder handles state persistence. This lets the user choose how offline data is stored while at the same time, abstracting away the actual storing of the data unless they want to. This solution would also help to mitigate any potential migration issues as the developer would be able to use their existing local storage provider.

Reprevise avatar Apr 09 '22 17:04 Reprevise

That state_rebuild persistence wiki looks very similar to what I plan on doing for Riverpod. Thanks for sharing!

rrousselGit avatar Jul 31 '22 14:07 rrousselGit

Supporting ChangeNotifier and the current StateNotifier may be difficult though.

I think I'll have to implement this feature only for the new StateNotifier syntax if that's OK.

rrousselGit avatar Aug 04 '22 17:08 rrousselGit

Supporting ChangeNotifier and the current StateNotifier may be difficult though.

I think I'll have to implement this feature only for the new StateNotifier syntax if that's OK.

@rrousselGit Can you clarify what you mean by the "new StateNotifier syntax"? Are you talking about the one that's already released in 2.0 dev or an unreleased one?

Reprevise avatar Aug 04 '22 17:08 Reprevise

An unreleased one.

rrousselGit avatar Aug 04 '22 17:08 rrousselGit

I've talked about the new syntax on twitter/discord before. It would be:

final provider = NotifierProvider<MyNotifier, int>(MyNotifier.new);

class MyNotifier extends Notifier<int> {
  @override
  int create() {
    ref.watch(something);
    return 0;
  }
}

With an async variant:

final provider = AsyncNotifierProvider<MyNotifier, int>(MyNotifier.new);

class MyNotifier extends AsyncNotifier<int> {
  @override
  Future<int> create() async {
    ref.watch(something);
    return 0;
  }
}

rrousselGit avatar Aug 04 '22 17:08 rrousselGit

I'll add offline persistence slightly after the 2.0 release

There will be a 2.1 about of month after the 2.0 with it. There's no reason to delay the 2.0 release since that's a new feature :smile:

rrousselGit avatar Aug 16 '22 18:08 rrousselGit

I'll add offline persistence slightly after the 2.0 release

There will be a 2.1 about of month after the 2.0 with it. There's no reason to delay the 2.0 release since that's a new feature 😄

when will version 2 be released?

miguelflores1993 avatar Aug 22 '22 20:08 miguelflores1993

Somewhere during september.

rrousselGit avatar Aug 22 '22 20:08 rrousselGit

I was going to implement my own version of riverpod x persisted state, but just discovered that it's in the official roadmap, can't wait :)

gabsn avatar Sep 06 '22 15:09 gabsn

Does the feature need to be delayed?

Adherentman avatar Sep 23 '22 02:09 Adherentman

No it's still plannes for 2.1

rrousselGit avatar Sep 23 '22 08:09 rrousselGit

What this issue involves will most likely be two bits:

  • new parameters on providers for decoding/encoding:
 final provider = SomeProvider(
  (ref) {},
  name: 'unique_key',
  decode: (data) => MyClass.fromJson(data),
  encode: (value) => value.toJson(),
);
  • a way to tell ProviderScope/ProviderContainer how to obtain the encoded value of a provider
ProviderScope(
  initialData: {
    'unique_key': { <some json> },
  },
  encoder: SomeDBEncoder(),
),

I think a common pattern might be:

  1. Fetch data from the server
  2. Cache fetched data locally

Then in the future:

  1. Supply provider with local cached value
  2. If you so desire, fetch the value from the server to repopulate cache

Would something like the following be a way of going about that (assuming we are using the new NotifierProvider syntax with riverpod_generator)?

Stream<TheDataType> build() async* {
  yield await fetchFromDisk(); // would this be needed? or will decode handle this?
  yield await fetchFromServer();
}

And then perhaps there will be a separate encode/persist method that will cache the value locally on state change as suggested?

Or would it be just simply:

Future<TheDataType> build() {
  ref.watch(cacheRepopulationTimerProvider); // force fetching new data at some set times, perhaps at app launch
  return fetchFromServer();
}

Because the decode would handle the fetching from disk and everything else for you before build is called (or would build be called at all if the encoder finds the value stored on disk? Maybe having some sort of check whether it should get called, like the cacheRepopulationTimerProvider above, would be ideal?).

In any case, I think the Stream generator syntax could be a clean way of handling multiple steps, but the second option with just the Future<TheDataType> shows intent better.

Edit: Perhaps the encoder SomeDBEncoder could actually handle fetching data from the server as well? But that would hurt readability/intent--someone reading the code might not know where the true data is coming from.

GregoryConrad avatar Oct 25 '22 23:10 GregoryConrad

I see https://pub.dev/packages/hydrated_riverpod extension which is supporting offline persistence support just like https://pub.dev/packages/hydrated_bloc for Bloc.

thought worth mentioning

przbadu avatar Nov 09 '22 03:11 przbadu

Any updates regarding this feature? Will it be soon released?

BilalMAddam avatar Nov 12 '22 07:11 BilalMAddam

Is there a recommended way to emulate this functionality before it's merged? I'm using AsyncNotifier with codegen and want to cache the latest network states using my selected storage library; currently just write to disk every time a _fetch() completes and hydrate in build(). Should I be using something like ref.onDispose inside build()? I don't use watch() inside build though, just await my network calls

Rizzaxc avatar Feb 02 '23 07:02 Rizzaxc

I got side-tracked by working on lint rules for Riverpod. I'll release those soon and see if I get pick this up next.

Although there are good candidates for other features, like #1660

rrousselGit avatar Feb 02 '23 12:02 rrousselGit

@Rizzaxc I recently implemented a workaround for my project that's working well so far. It's using disk storage to save revisions on every change event and restores the latest revision when the app starts: https://github.com/kwiesmueller/cypher_sheet/blob/main/lib/state/observer.dart

It's my first time using riverpod, but this has been working well since I added it.

kwiesmueller avatar Feb 02 '23 14:02 kwiesmueller

@rrousselGit What problem feature solve? Is this meant as a tool to enable an offline first app functionality? Allow groundwork for some kind of custom database online/offline sync? Or as a more flexible and feature rich local storage?

I don't think I fully understand what all this would do.

FXschwartz avatar Feb 03 '23 19:02 FXschwartz

@FXschwartz Riverpod's goal in general is to do as much as possible for typical complex apps.

So ideally it should work for offline first apps.

rrousselGit avatar Feb 03 '23 20:02 rrousselGit

@FXschwartz Riverpod's goal in general is to do as much as possible for typical complex apps.

So ideally it should work for offline first apps.

Would you ever consider adding on to this some kind of handler that would react to the app going online/offline?

Right now one of Firestore's biggest advantages is that is handles offline/online data syncing for you without having to do anything extra. If you want this functionality with another database like Mongodb for instance you'd have to create your own custom data syncing.

Not sure if that scope would be more than Riverpod should be responsible for but I think it would be really valuable.

FXschwartz avatar Feb 03 '23 21:02 FXschwartz