react-native-mmkv-storage: [Bug] Data missing from storage after app restart

Describe the bug We received in the past few months many complaints about users being logged out from the app. After adding logging and investigating the incidents we found out that at random the getStringAsync returns undefined after the app is restarted. If we restart the app again the getStringAsync returns the expected value. By restart I mean the app is closed by use with swipe up or is closed by to os and the user starts it again at a later time.

To Reproduce

Steps to reproduce the behavior:

  1. Initialize the storage with the following
this.storage = new MMKVStorage.Loader()
  .withServiceName('com.test.app1.TestV1')
  .withInstanceID('TestV1')
  .withEncryption()
  .initialize();
  1. Call this.storage.setStringAsync('token', tokenValue); on login to store the secret token
  2. Call this.storage.getStringAsync('token'); on each app start, if the call returns a value it means the user is logged in
  3. On some restarts at random calling this.storage.getStringAsync('token'); returns undefined after the value is set.

Expected behavior After calling setStringAsync, all the calls to getStringAsync should return the value set.

Platform Information:

  • OS: iOS
  • React Native Version: 0.65.1
  • Library Version v0.6.6

Additional context We are also curious if other people experience the same issue?

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 2
  • Comments: 128 (99 by maintainers)

Most upvoted comments

We might have a potential fix to this issue. I will work on it asap.

Fix shipped in v0.7.0 Thanks @JoniVR for the insane debugging and finally fixing this too.

I’ve currently logged it like this in the latest test build (inside index.js):

if (typeof global.getStringMMKV !== 'function') {
  crashlytics().log(
    `global.getStringMMKV value: ${global.getStringMMKV}, see: https://github.com/ammarahm-ed/react-native-mmkv-storage/issues/195`,
  );
  crashlytics().recordError(
    new Error('typeof global.getStringMMKV is not a function'),
  );
} else {
  crashlytics().log(`MMKV storage initialized: ${global.getStringMMKV}`);
}

In case anyone else wants to use it.

Now, let’s hope a user runs into it again, should generate an automatic crashlytics report. If not, a manual report will confirm through logging that the function works as intended but the issue might be elsewhere.

Hello, I have worked on a fix after the issue #207 was opened which brought me to a new and more predictable way to install JSI bindings. Basically the potential fix is taken from https://github.com/mrousavy/react-native-mmkv.

It’s not released yet but you should test this with the master branch:

yarn add https://github.com/ammarahm-ed/react-native-mmkv-storage.git

After installation you will need to remove previous linking on Android.

MainApplication.java: Remove the commented out lines as below.

package com.example;

import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
//import com.facebook.react.bridge.JSIModulePackage; <---------------
import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
//import com.ammarahmed.mmkv.RNMMKVJSIModulePackage; <---------------
public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost =
      new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
          return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for example:
          // packages.add(new MyReactNativePackage());
          return packages;
        }

        @Override
        protected String getJSMainModuleName() {
          return "index";
        }

      //    @Override <---------------
      //    protected JSIModulePackage getJSIModulePackage() { <---------------
      //        return new RNMMKVJSIModulePackage(); <---------------
       //   } <---------------
      };

  @Override
  public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
  }

  ...
}

If you are using reanimated@v2 then you should remove the modified CustomRNMMKVJSIModulePackage in android/app/main/java/com/yourappid/ and remove any references to it in MainApplication.java.


**For iOS just run**:
```pod install

Important Note: Once you get the iOS build running. Drop a comment here and let me know that storage is working in debug and you are able to read/write data. I don’t have a Mac with me at the moment so need a little help here.

@ammarahm-ed Done, let me know if anything else needs to be done 😃

@jaltin I am working on it.

Update: So far so good, no reports yet (neither manual nor automatic) 😄

@ammarahm-ed No problem, it’s easily misread and dispatch_sync is not used that often compared to the async variant. I deployed the possible fix about 48 hours ago, nothing reported so far… let’s hope it remains that way, usually takes a few days before they start popping up 🤞🏻

I also think that 250 is a very big value, it should check every 10-50ms instead with iterations ranging 10-50. So response is instant.

I agree with you, will change it after confirming it solves the issue (or just whenever the fix is implemented in the library) 😀

@JoniVR dispatch_async will not work since it is async and the native call is synchronous so while you are trying to delay the return value, it will not be delayed actually. However doing the same in setItem or getItem in JS will work since they are async.

In native objc++ code, maybe we can use std::thread thread_obj(lamda) and then call thread_obj.join() to make it wait for the result before returning value.

I have been a bit busy. I plan to add a fix on Sunday which should work properly.

Hi @ammarahm-ed!

Just had a report where getString didn’t return null but undefined. See logs:

# Crashlytics - Custom logs
# Application: be.frontforce.emergency.mobile
# Platform: apple
# Version: 1.2.1 (88)
# Date: Mon Feb 21 2022 00:14:47 GMT+0100

 0 | Mon Feb 21 2022 00:13:19 GMT+0100 | DEBUG | index.js | MMKV storage initialized: true
 1 | Mon Feb 21 2022 00:13:19 GMT+0100 | DEBUG | MMKV:MMKVStorage.getCurrentMMKVInstanceIDs | {"default":false}
 2 | Mon Feb 21 2022 00:13:19 GMT+0100 | DEBUG | MMKV:encryptedStorage.getAllMMKVInstanceIDs | default
 3 | Mon Feb 21 2022 00:13:19 GMT+0100 | DEBUG | MMKV:global.secureKeyExists | true
 4 | Mon Feb 21 2022 00:13:19 GMT+0100 | DEBUG | MMKV:encryptedStorage.getString | undefined
 5 | Mon Feb 21 2022 00:13:19 GMT+0100 | ERROR | MMKV:encryptedStorage.getString | Expected null is not null
 6 | Mon Feb 21 2022 00:13:20 GMT+0100 | DEBUG | persistErrorHandlerMiddleware | Rehydrating auth, token: null
 7 | Mon Feb 21 2022 00:13:20 GMT+0100 | DEBUG | App | Notification permission status: 1
 8 | Mon Feb 21 2022 00:13:20 GMT+0100 | DEBUG | setMessagingToken | OS: ios, Token: cr5hJb...5x9KLv
 9 | Mon Feb 21 2022 00:13:21 GMT+0100 | DEBUG | httpInterceptor:responseHandler | {
"requestUrl": "/endpoints.emergency.json",
"responseCode": 200,
"bearer": null
}

@ammarahm-ed I did, they seem to all be printing the expected values:

 DEBUG  2022-02-09T17:52:30 | index.js | MMKV storage initialized: true
 DEBUG  2022-02-09T17:52:30 | MMKV:MMKVStorage.getCurrentMMKVInstanceIDs | {"default":true}
 DEBUG  2022-02-09T17:52:30 | MMKV:encryptedStorage.getAllMMKVInstanceIDs | default
 DEBUG  2022-02-09T17:52:30 | MMKV:global.secureKeyExists | true
 DEBUG  2022-02-09T17:52:30 | MMKV:encryptedStorage.getString | null

I’ve set up automated crashlytics reporting for unexpected values again, let bug hunting season recommence! 😄

Most welcome. Do let me know if you face any other issues

@bombillazo Seems like your metro cache is old causing a problem when you upgrade loading old bundled js files. Restart metro bundler with cache reset:

yarn start --reset-cache

If you don’t know how that works. Just remove node_modules folder and run npm/yarn install

And now everything should work as expected.

@bombillazo Seems like everything is updated correctly. Try this:

import {init,isLoaded} from "react-native-mmkv-storage";

console.log(init(),isLoaded(), global.setStringMMKV)

@bombillazo

run ./gradlew clean on /android folder. Delete node_modules. Delete app from your phone. Then run yarn install or npm install. Finally install the app again and everything should be good.

Also you don’t need to run npx mmkv link on RN 0.67 and above.

Also make sure to run pod install on iOS.

Did this happen on iOS or Android?

Yes you don’t need this. However if you don’t remove it, everything should still work without changing anything. @bombillazo

@ammarahm-ed , I also tested it (for android, as that is where storage the issue was for my project) and the issue seems resolved as of the latest update the storage is working again and saving/retrieving my users. In the debug build and release build, both are working fine. Thanks for looking into it. really appreciate it.

OS: Android

@JoniVR just log isLoaded function’s result. It’s basically doing the same thing you were doing above manually. true means that everything is working.

Currently a project i’m working on has been having this issue also ( but for the android version only, ios is working fine ). As mentioned above by @ammarahm-ed i tried logging : Which returns undefined (both have been used in App.js) console.log("storage initialized: ", global.getStringMMKV); and also returns undefined await MMKV.setStringAsync("test", "some txt"); , const test = await MMKV.getStringAsync("isLoggedIn"); console.log('signin.js test: ', test);

Any additional info needed?

Platform Information:
OS: Android
react-native: 0.63.4
react-native-mmkv-storage: ^0.5.8,

@JoniVR The very basic thing you should log:

console.log("storage initialized: ", global.getStringMMKV);

This should return [Function ...] always. The most probable reason for this being undefined could be MMKV not registering at runtime. Add this and next time a user reports. Check to logs to see if storage was initialized or not.

@JoniVR I will post here a list of things to Log. That will help in debugging.