Skip to content

Instantly share code, notes, and snippets.

@export-mike
Created June 18, 2018 08:22
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save export-mike/3df33844c5351c5df802cd99459f20c4 to your computer and use it in GitHub Desktop.
Save export-mike/3df33844c5351c5df802cd99459f20c4 to your computer and use it in GitHub Desktop.
Unstated Offline Container
import * as Unstated from 'unstated';
import debounce from 'lodash.debounce';
import clonedeep from 'lodash.clonedeep';
import { Alert } from 'react-native';
import AsyncStorage from '../AsyncStorage';
const noop = () => {};
//set FS storage limit for android: https://codedaily.io/tutorials/4/Increase-Android-AsyncStorage-Size-in-React-Native
export class Container extends Unstated.Container {
debounce = 800;
constructor({ key } = {}) {
super();
this.key = key;
if (!key) {
if (process.env.NODE_ENV === 'development') {
console.warn(
'Storage for Container not enabled, provide a name property'
);
}
this.setState = super.setState; // disable override saving
} else {
this.restoreState(this.key)
.then(state => {
if (state) {
super.setState(state);
}
})
.catch(e => {
console.warn('Error hydrating from storage', e);
});
}
}
setState(state, callback = noop) {
if (!this.initialState) {
this.initialState = clonedeep(this.state);
}
let newState = state;
if (typeof state === 'function') {
newState = state(this.state);
}
super.setState(newState, callback);
this.setItem(this.key, newState);
}
setStateSkipPersist(state, callback = noop) {
super.setState(state, callback);
}
restoreState = async () => {
try {
const result = await AsyncStorage.getItem(this.key);
if (!result) return this.state;
return JSON.parse(result);
} catch (e) {
console.log(e);
console.warn(`Error getting Item to restore State ${this.key}`, e);
return this.state;
}
};
setItem = debounce(async (key, state) => {
try {
await AsyncStorage.setItem(key, JSON.stringify(state));
} catch (e) {
if (e.message.includes('database or disk is full')) {
Alert.alert('Disk space is full, offline features will be degraded');
}
console.warn(`Error Setting Item ${key}`, e, state);
}
}, this.debounce);
}
@export-mike
Copy link
Author

with AsyncStorage having a different implementation for Android to avoid FS Storage limits.

// The Android AsyncStorage api is limited to mbs in storage.
// despite updating the android limit in MainApplication.java
// I've opted keep to the AsyncStorage interface to use a manual persistent storage
// this way we have full control over any issues that might crop up in the future.
import RNFetchBlob from 'react-native-fetch-blob';
//https://github.com/facebook/react-native/issues/12529
const DocumentDir = RNFetchBlob.fs.dirs.DocumentDir;
const storagePath = `${DocumentDir}/persistStore`;
const encoding = 'utf8';

const toFileName = (name: string) => name.split(':').join('-');
const fromFileName = (name: string) => name.split('-').join(':');

const pathForKey = (key: string) => `${storagePath}/${toFileName(key)}`;

const AndroidFileStorage = {
  setItem: (key: string, value: string, callback?: ?(error: ?Error) => void) =>
    new Promise((resolve, reject) =>
      RNFetchBlob.fs
        .writeFile(pathForKey(key), value, encoding)
        .then(() => {
          if (callback) {
            callback();
          }
          resolve();
        })
        .catch(error => {
          if (callback) {
            callback(error && error);
          }
          reject(error);
        })
    ),
  getItem: (
    key: string,
    callback?: ?(error: ?Error, result: ?string) => void
  ) =>
    new Promise(resolve =>
      RNFetchBlob.fs
        .readFile(pathForKey(toFileName(key)), encoding)
        .then(data => {
          if (callback) {
            callback(null, data);
          }
          resolve(data);
        })
        .catch(error => {
          console.warn(error);
          if (callback) {
            callback(null);
          }
          resolve(null);
        })
    ),
  removeItem: (key: string, callback?: ?(error: ?Error) => void) =>
    new Promise((resolve, reject) =>
      RNFetchBlob.fs
        .unlink(pathForKey(toFileName(key)))
        .then(() => {
          if (callback) {
            callback();
          }
          resolve();
        })
        .catch(error => {
          console.warn(error);
          if (callback) {
            callback(error);
          }
          reject(error);
        })
    ),
  getAllKeys: (callback?: ?(error: ?Error, keys: ?Array<string>) => void) =>
    new Promise((resolve, reject) =>
      RNFetchBlob.fs
        .exists(storagePath)
        .then(
          exists =>
            exists ? Promise.resolve() : RNFetchBlob.fs.mkdir(storagePath)
        )
        .then(() =>
          RNFetchBlob.fs
            .ls(storagePath)
            .then(files => files.map(file => fromFileName(file)))
            .then(files => {
              if (callback) {
                callback(null, files);
              }
              resolve(files);
            })
        )
        .catch(error => {
          if (callback) {
            callback(error);
          }
          reject(error);
        })
    ),
  clear: async () => {
    const keys = await AndroidFileStorage.getAllKeys();
    return Promise.all(keys.map(k => AndroidFileStorage.removeItem(k)));
  },
};

export default AndroidFileStorage;

IOS:
Storage using react-native AsyncStorage

import { AsyncStorage } from 'react-native';
export default AsyncStorage;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment