16

I need to access data stored in AsyncStorage from iOS native Objective C code.

This is needed to get the data in sync instead of sending App event to JS and then send it back to native code.

Kolors
  • 717
  • 1
  • 7
  • 23
Prasad Sawant
  • 221
  • 4
  • 10

4 Answers4

4

I've just been faced with the same problem.

My solution was to move the code native side.

On iOS:

#import <React/RCTAsyncLocalStorage.h>
#import <React/RCTBridgeModule.h>

RCTResponseSenderBlock completion = ^(NSArray *response) {
  NSString *theme = response[1][0][0][1];

  // Setup RCTRootView with initialProperties
};

RCTAsyncLocalStorage *storage = [[RCTAsyncLocalStorage alloc] init];

dispatch_async(storage.methodQueue, ^{
  [storage performSelector:@selector(multiGet:callback:) withObject:@[@"theme"] withObject:completion];
});

You could additionally use dispatch_semaphore_wait to perform this synchronously

Update:

I needed the variable in the global state not just in the component props so the above doesn't go far enough.

I've had to work this into the React Native source at the point that the Javascript source is loaded.

NSString *javascriptPrepend = [NSString stringWithFormat:@"var ThemeMode = '%@';", self.theme];
NSMutableData *prependData = [[javascriptPrepend dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
[prependData appendData:sourceCode];

sourceCode = prependData; 

I'll see if they're open to a PR to allow this kind of functionality and post back here if I get it through.

Luke Rhodes
  • 293
  • 3
  • 13
1

Enhancing @luke’s solution – fixing an issue with the location of the data in the response; converting the JSON data to NSDictionary and ensuring the safety of the code – here is the complete method:

+(void)jsonFromLocalRNStrogeForKey:(NSString *)key completion:(void (^)(NSDictionary * _Nullable, NSError * _Nullable))completion
{
  RCTResponseSenderBlock rnCompletion = ^(NSArray *response) {

    NSString *jsonAsString;

    if (response.count > 1) {
      NSArray *response1 = response[1];
      if (response1.count > 0) {
        NSArray *response2 = response1[0];

        if (response2.count > 1) {
          jsonAsString = response2[1];
        }
      }
    }

    NSData *jsonAsData = [jsonAsString dataUsingEncoding:NSUTF8StringEncoding];

    NSError *error;

    NSDictionary *json = [NSJSONSerialization
                          JSONObjectWithData:jsonAsData                                                          options:NSJSONReadingMutableContainers                                                             error:&error];

    completion(json, error);
  };

  RCTAsyncLocalStorage *storage = [RCTAsyncLocalStorage new];

  dispatch_async(storage.methodQueue, ^{
    [storage performSelector:@selector(multiGet:callback:) withObject:@[key] withObject:rnCompletion];
  });
}
ofer2980
  • 464
  • 5
  • 26
  • Can you give an example of how to call the method? I need do to the [same thing](https://stackoverflow.com/a/48165649/1958882) that I did on Android but now for iOS. – Pablo Jan 12 '18 at 17:45
  • 1
    Let’s say that you define the function on a class named Utils. Then let’s you’d like to fetch the current user which you stored previously. Then you’ll do it like this: [Utils jsonFromLocalRNStrogeForKey:@"USER_KEY" completion:^(NSDictionary * _Nullable json, NSError * _Nullable error) { // do something with the data (json) }]; – ofer2980 Jan 13 '18 at 18:20
  • That worked, but I believe that now the problem it's to find the right **root key**. Like I said, on Android I had start from ```catalystLocalStorage``` where the entire **data** was only in the first row as __json string__, while on iOS I tried ```user``` and ```group.MyApp``` but ```jsonAsString``` it's always null. Do you have any idea what could be the main key for iOS? – Pablo Jan 15 '18 at 13:06
  • [Here](https://i.imgur.com/GhM3sQr.png), in every possible key that I have I'm getting null. – Pablo Jan 15 '18 at 13:52
  • Is it possible you take a look at this https://stackoverflow.com/questions/48280006/using-rctasynclocalstorage-getallkeys? – Pablo Jan 16 '18 at 15:07
  • Can I used the same code to save data in async storage by replacing **multiGet** to **multiSet**? – Website Is Fun Oct 04 '18 at 03:17
1

My current solution. Which technically isn't a direct answer to the question but does offer a work-around for sharing data between Javascript and native code, is to use NSUserDefaults.

Using this package, react-native-default-preferences to easily persist the data into NSUserdefaults and then on the native side easily retrieve them as normal.

For my purposes I am persisting a token retrieved in Javascript, to be used later by extensions of the app. This means I also persist to a UserDefaults suite using the same key as my app.

React Native (Javascript):

DefaultPreference.setName('group.myApp');
DefaultPreference.set('TOKEN', token);

Objective-C:

NSString *TOKEN = [[[NSUserDefaults alloc] initWithSuiteName:@"group.myApp"] stringForKey:@"TOKEN"];
Kolors
  • 717
  • 1
  • 7
  • 23
  • Does ```group.myApp``` it's some default name used by iOS? Should I call ```group.TheApp``` if the app name is __TheApp__? – Pablo Jan 15 '18 at 12:55
  • You only need to set the name if you are sharing data between a suite of shared apps. For example, a share extension and your main app. You first need to setup a group, then when you initialise your NSUserDefaults, use the name which will follow the format `group.YOUR_APPS_NAME` to access the same shared NSUserDefaults. – Kolors Jan 16 '18 at 16:40
  • More info on setting up an app group can be found here http://blog.intrepid.io/ios-app-extensions – Kolors Jan 16 '18 at 16:42
  • Hey man! Well the problem was resolved [here](https://stackoverflow.com/questions/48232786/how-can-i-return-nsjsonserialization?noredirect=1&lq=1) and [here](https://stackoverflow.com/questions/48280006/using-rctasynclocalstorage-getallkeys). But thanks anyway. – Pablo Jan 16 '18 at 16:44
-2

I think you can use the code from the implementation AsyncStorage for this purpose. You can see it here. It basically loads the files that store the data using a NSFileManager and then parses them accordingly. In your case, have a look for instance at the multiGet method and then trace all the required functions present in the file. With that you should be able to re-implement them (or adapt) to fit your needs.

martinarroyo
  • 9,389
  • 3
  • 38
  • 75