Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react-native-async-storage support #87

Closed
pkrinesh opened this issue Jan 11, 2023 · 10 comments
Closed

react-native-async-storage support #87

pkrinesh opened this issue Jan 11, 2023 · 10 comments

Comments

@pkrinesh
Copy link

Hi, I have been using it with expo managed project and loving it. but missing the async-storage support. How can we use it with async-storage?

@jmeistrich
Copy link
Contributor

We could potentially add an async-storage plugin. But mmkv should be much better for performance, especially because it loads synchronously so it's better for startup time. Are you unable to use mmkv or is there a reason you prefer not to use it?

@pkrinesh
Copy link
Author

We have used mmkv before but it is not supported in the expo-managed workflow. To get started quickly we can use async-store initially and then can replace it with mmkv. I tried to do it myself but not working properly become of async behavior or I may be missing something.

@jmeistrich
Copy link
Contributor

Oh, I thought it does work in expo, and a search makes it seem like it does? 🤷

If you're trying to write the plugin yourself, to get it to work with the async behavior you'd want to use the async initialize function to preload the data into memory, and then the get* functions would just return the value from local values. You could mostly copy the mmkv or local storage plugins from there.

I can add it to my todo list but I'm not sure when I can get to it. If a lot of others are needing it I can push it up the priority queue :). If you want to give it a stab yourself and submit a PR I can help with any questions or add some tweaks to finalize it, if you want?

@pkrinesh
Copy link
Author

sure I will try and submit a PR

thanks

@djMax
Copy link

djMax commented Jan 15, 2023

Here's what happens in expo:

Error: react-native-mmkv is not supported in Expo Go! Use EAS (`expo prebuild`) or eject to a bare workflow instead.

@alanjhughes
Copy link

MMKV is supported in managed expo, it just cannot be used with expo go. It can be added by installing and then building a custom dev client. I am using this in two managed apps and it works perfectly. Expo go is not intended as a development platform for your entire application. Support in Expo go should not be confused with managed workflow support.

@PierrePetiteau
Copy link

PierrePetiteau commented Aug 1, 2023

Not sure if it's still relevant but I build my own implem, hope it can help

// ObservablePersistAsyncStorage.ts

import type { Change, ObservablePersistLocal, PersistMetadata } from '@legendapp/state';
import { setAtPath } from '@legendapp/state';
import AsyncStorage from '@react-native-async-storage/async-storage';

/**
 * Can be replaced by MMKV official plugin (for synchronous persistency) as soon as you quit expo go
 */

const MetadataSuffix = '__m';

export class ObservablePersistLocalAsyncStorage implements ObservablePersistLocal {
  private data: Record<string, any> = {};

  // initialize() method allow us rehydrating data before calling getTable()
  public async initialize() {
    const tables = await AsyncStorage.getAllKeys();
    const values = await AsyncStorage.multiGet(tables);

    values.forEach(([table, value]) => {
      this.data[table] = value ? JSON.parse(value) : undefined;
    });
  }

  public getTable(table: string) {
    if (this.data[table] === undefined) {
      try {
        // Unusual async process here - let's be vigilant 👀
        (async () => {
          const value = await AsyncStorage.getItem(table);
          this.data[table] = value ? JSON.parse(value) : undefined;
        })();
      } catch {
        console.error('[legend-state] ObservablePersistLocalAsyncStorage failed to parse', table);
      }
    }
    return this.data[table] ?? {};
  }
  public getMetadata() {
    return {};
  }
  public async get(table: string, id: string) {
    if (this.data[table] === undefined) {
      try {
        const value = await AsyncStorage.getItem(table);
        this.data[table] = value ? JSON.parse(value) : undefined;
        return this.data[table]?.[id];
      } catch {
        console.error('[legend-state] ObservablePersistLocalAsyncStorage failed to parse', table);
      }
    }
  }
  public set(table: string, changes: Change[]): void {
    if (!this.data[table]) {
      this.data[table] = {};
    }

    for (let i = 0; i < changes.length; i++) {
      const { path, valueAtPath, pathTypes } = changes[i];
      this.data[table] = setAtPath(this.data[table], path as string[], pathTypes, valueAtPath);
    }
    this.save(table);
  }
  public updateMetadata(table: string, metadata: PersistMetadata) {
    return this.setValue(table + MetadataSuffix, metadata);
  }
  public async deleteTable(table: string) {
    await AsyncStorage.removeItem(table);
  }
  public deleteMetadata(table: string) {
    this.deleteTable(table + MetadataSuffix);
  }

  // Private
  private async setValue(table: string, value: any) {
    this.data[table] = value;
    await this.save(table);
  }
  private async save(table: string) {
    const v = this.data[table];

    if (v !== undefined && v !== null) {
      await AsyncStorage.setItem(table, JSON.stringify(v));
    } else {
      await AsyncStorage.removeItem(table);
    }
  }
}
// authState.ts

import { computed, observable } from '@legendapp/state';
import { persistObservable } from '@legendapp/state/persist';

import { ObservablePersistLocalAsyncStorage } from '@src/states/persistence/ObservablePersistAsyncStorage';

export type AuthState = { };

export const authState = observable<AuthState>({ });

persistObservable(authState, { local: 'authState', persistLocal: ObservablePersistLocalAsyncStorage });

@jmeistrich
Copy link
Contributor

Thank you @PierrePetiteau, that's very helpful! What does the get function do? That's not part of the ObservablePersistLocal interface so I don't think that would be used?

@ice-cap0
Copy link

ice-cap0 commented Aug 23, 2023

hey so react-native-mmkv is fast but not designed to handle large sets of data.
The benefit of it being synchronous works because it loads everything into memory, which crashes apps that hit the limit.
Also AsyncStorage still has 2x the download volume vs react-native-mmkv

@jmeistrich
Copy link
Contributor

Oops, we added AsyncStorage support a while ago but I forgot to mention it in this issue. So I’m going to close this issue now :)

See https://legendapp.com/open-source/state/v3/sync/persist-sync/#asyncstorage-rn

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants