Skip to content

Latest commit

 

History

History
214 lines (164 loc) · 6 KB

asyncstorage.mo.md

File metadata and controls

214 lines (164 loc) · 6 KB

[MO] Preloading MobX data with AsyncStorage

Control Points

{% hint style='success' %}

If you want to use another storage backend than asyncStorage, you'll still need to check that

{% endhint %}

  • You persist data at least before the application is closed
  • You hydrate your application with the persisted data before you display the content
  • You burst the cache when needed (on logout for personnal information for instance)

Motivation

  • You want some data to be persisted accross application restart.

Prerequisites

We need to import AsyncStorage from react-native.

The following example will use three simple methods:

  • AsyncStorage.setItem(key, value)
  • AsyncStorage.getItem(key)
  • AsyncStorage.removeItem(key)

key and value are two strings, so don't pass a Javascript object in these functions! All AsyncStorage methods return a Promise object.

Steps (~15 minutes)

It is recommended to use an extra layer of abstraction on top of the bare AsyncStorage that takes into account the particularities of the data you want to store. Let's start with a simple example where the hotel bookings I want to store have this structure:

  booking
  |- id
  |- bookingDate
  |- clientId
  |- roomId
  |_ isPaymentConfirmed

roomId and clientId refer to the following objects:

  client
  |- id
  |_ name
  room
  |- id
  |- number
  |_ isAvailable

You have two options from this point:

  1. Storing simple values

In this example, we want to display basic info about a current booking, such as bookingDate, clientName, and roomNumber. Let's first define a extra layer to list our AsyncStorage keys, in a myAsyncStorage.js file for example:

import { AsyncStorage } from "react-native";

const BOOKING_DATE = "BOOKING_DATE";
const CLIENT_NAME = "CLIENT_NAME";
const ROOM_NUMBER = "ROOM_NUMBER";

export const asyncStorageKeys = {
  BOOKING_DATE,
  CLIENT_NAME,
  ROOM_NUMBER
};

These keys can be used to save/fetch data on the device in a bookings.js MobX store:

import { observable, action } from 'mobx'
import { AsyncStorage } from 'react-native'
import { asyncStorageKeys } from 'myApp/src/services/myAsyncStorage'

class BookingsStore {
  constructor () {
    AsyncStorage.getItem(asyncStorageKeys.BOOKING_DATE)
      .then(bookingDate => {
        if (bookinDate) {
          this.bookingDate = bookingDate
        }
      })

      AsyncStorage.getItem(asyncStorageKeys.CLIENT_NAME)
      .then(clientName => {
        if (clientName) {
          this.clientName = clientName
        }
      })

      AsyncStorage.getItem(asyncStorageKeys.ROOM_NUMBER)
      .then(roomNumber => {
        if (roomNumber) {
          this.roomNumber = roomNumber
        }
      })
  }
  @observable bookingDate = null
  @observable clientName = ''
  @observable roomNumber = null

  @action setRoomNumber(roomNumber) {
    this.rootNumber = roomNumber
    AsyncStorage.setItem(asyncStorageKeys.ROOM_NUMBER, roomNumber)
  }

  @action clearRoomNumber(roomNumber) {
    this.rootNumber = null
    AsyncStorage.removeItem(asyncStorageKeys.ROOM_NUMBER, roomNumber)
  }
  }
}

This approach works fine until you want to preload data in the form of objects and not strings. e.g. multiple bookings. Fortunately there is a workaround to solve this issue.

  1. Storing complex objects in JSON strings

We mentioned earlier that you shouldn't pass anything else than a string to AsyncStorage.setItem. You can however use JSON.stringify to convert your JavaScript objects to serialized JSON strings for storage. The original objects can be recovered using the JSON.parse method. Our myAsyncStorage.js file becomes:

import { AsyncStorage } from 'react-native'

const BOOKINGS = 'BOOKINGS'
const CLIENT = 'CLIENT'
const ROOMS = 'ROOMS'

export const asyncStorageKeys = {
  BOOKINGS,
  CLIENT,
  ROOMS
}

export const getObjectFromAsyncStorage = (itemName) => {
  return AsyncStorage.getItem(itemName)
  .then(item => {
    if(item) JSON.parse(item)
    else {
      reject(Error('Empty result'))
    }
  ).catch(error => console.log(error))
}

export const setObjectInAsyncStorage = (key, value) => {
  const valueString = JSON.stringify(value)
  AsyncStorage.setItem(key, value)
}

Notice how the file has become more manageable and easy to read. To manage the conversion between Javascript objects and the strings that AsyncStorage receives, it is recommended to define a couple of getter/setter helper functions. You will avoid systematically calling JSON.parse and JSON.stringify outside myAsyncStorage.js, keeping your code even more readable:

import { observable, action } from 'mobx'
import { AsyncStorage } from 'react-native'
import { asyncStorageKeys, getObjectFromAsyncStorage, setItemInAsyncStorage } from 'myApp/src/services/myAsyncStorage'

class BookingsStore {
  constructor () {
    getObjectFromAsyncStorage(asyncStorageKeys.BOOKINGS)
    .then(bookings.map(booking =>
      this.bookings.push = booking)
    }))

    getObjectFromAsyncStorage(asyncStorageKeys.CLIENT)
    .then(client => {
      this.client = client
    })

    getObjectFromAsyncStorage(asyncStorageKeys.ROOMS)
    .then(rooms.map(room => {
      this.rooms.push(room)
    }))
  }
  @observable bookings = []
  @observable client = null
  @observable rooms = []

  @action leaveRoom(roomId) {
    this.rooms = this.rooms.map(room => {
      if (room.id === roomId) {
        room.isAvailable = true
      }
      return room
    })
    setItemInAsyncStorage(asyncStorageKeys.ROOMS, this.rooms)
  }

  @action clearRooms(roomNumber) {
    this.rooms = []
    AsyncStorage.removeItem(asyncStorageKeys.ROOMS)
  }
  }
}

Notice that by using Promise rejection in the getObjectFromAsyncStorage method, we can avoid checking whether loaded items are empty, thus refactoring our code.

Troubleshooting and extras

There are of course many more ways to use asyncStorage, if you want to explore them visit this page