react-native: AsyncStorage Couldn't read row 0, col 0 from CursorWindow

Description

When reading values that are too large using AsyncStorage on Android I run into this error:

I wrote a large value with AsyncStorage and later tried to read it, but it crashed.

BaseError: Couldn't read row 0, col 0 from CursorWindow.  Make sure the Cursor is initialized correctly before accessing data from it.
  at e (/node_modules/react-native/Libraries/Storage/AsyncStorage.js:423:22)
  at None (/node_modules/react-native/Libraries/Storage/AsyncStorage.js:416:71)
  at map ([native code])
  at errors (/node_modules/react-native/Libraries/Storage/AsyncStorage.js:416:51)
  at slicedToArray (/node_modules/react-native/Libraries/Storage/AsyncStorage.js:54:33)

Reproduction

It looks like on iOS we write to disk when the value is above a threshold and on Android we write to sqllite always.

let x = 'x';
while (x.length <= 3194304) {
  x = `${x}${x}`;
}
await AsyncStorage.setItem('@@GARBAGE@@', x); // works
const garbage = await AsyncStorage.getItem('@@GARBAGE@@'); // throws

Solution

In order for me to fix this I’ll need to re-write AsyncStorage to write to disk instead of writing to sql. https://github.com/mvayngrib/react-native-safe-async-storage <— this guys has the right idea but his code is super old and broken.

Additional Information

  • React Native version: 0.39.2
  • Platform: Android
  • Operating System: Samsung Galaxy S4

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 15
  • Comments: 18 (8 by maintainers)

Most upvoted comments

This is still a problem in the latest RN, AFAIK

Still happening, ussing

"react": "16.4.0",
"react-native": "0.55.4",

Can confirm that this is still an issue on 0.53 - basically is still valid what @skv-headless linked and the solution proposed by @ThaJay is prob the best ‘workaround’ at the moment.

Only side-issue with using redux-persist-filesystem-storage is that it’s a bit ‘massive’, meaning that it would reset all the saved data if deployed as an update on an existing app (as is in our scenario). Any ideas on how to implement it in a non-destructive way? (writing a migration or something?)

this still happening in newer versions right? I’m using RN 41

@hramos It is not fixed for sure. Here is a small a explanation https://stackoverflow.com/a/21432966/1410905. Maybe it worth mentioning in the docs that max size for sqlite cursor is 2mb?

In docs said On Android, AsyncStorage will use either RocksDB or SQLite based on what is available would be great to know how to switch to RocksDB?

I’ve added this to a AsyncStorage.android.js file which I now use instead. I have a relatively large value to set.

Thanks to @robwalkerco

https://github.com/rt2zz/redux-persist/issues/284#issuecomment-291874254

/**
* @flow
*/

import RNFetchBlob from 'react-native-fetch-blob'

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, reject) =>
      RNFetchBlob.fs.readFile(pathForKey(toFileName(key)), encoding)
        .then(data => {
          if (callback) {
            callback(null, data)
          }
          resolve(data)
        })
        .catch(error => {
          if (callback) {
            callback(error)
          }
          reject(error)
        })
  ),
  removeItem: (
    key: string,
    callback?: ?(error: ?Error) => void,
  ) =>
    new Promise((resolve, reject) =>
      RNFetchBlob.fs.unlink(pathForKey(toFileName(key)))
        .then(() => {
          if (callback) {
            callback()
          }
          resolve()
        })
        .catch(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)
      })
  ),
}

export default AndroidFileStorage

whilst AsyncStorage.ios.js

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

It’s still an issue when trying to read something large from AsyncStorage. Please consider an error on write instead.

also: https://github.com/rt2zz/redux-persist/issues/284 https://github.com/robwalkerco/redux-persist-filesystem-storage (redux-persist is not needed to use redux-persist-filesystem-storage)

Oh I’m sorry I meant to say 0.39.2 (Updated original post)