Mitch
Mitch

Reputation: 1507

How to detect when a React Native app is opened?

My React Native app wants to synchronize its local data with an API when the user opens the app. This should happen whenever the user returns to the app, not just when it first starts. Essentially, what I would like is the equivalent of AppDelegate's applicationDidBecomeActive callback, so that I can run synchronization code there. Obviously, I would like to do this in React Native instead.

As far as I can tell, the componentWillMount / componentDidMount callbacks on the root component only run when the app is initially loaded, not after the user leaves the app and comes back later (without explicitly quitting the app).

I thought that the AppState API would provide this functionality, but its change listeners don't fire in this case, either.

This seems like obvious functionality to have, so I must be missing something glaringly obvious. Help!

Upvotes: 46

Views: 31912

Answers (3)

Artal
Artal

Reputation: 9143

I don't think that you're missing something. This functionality is just not provided by react-native out of the box. Maybe the idea was to simplify, since for most apps it's enough to perform data sync when the app returns to foreground.

You can either create your own native module which provides this functionality or you can go with a simple (sort of hacky?) solution:

In your AppDelegate save a reference to the react root view:

@interface AppDelegate()
@property (nonatomic, strong) RCTRootView *rootView;
@end

When initializing the view, set your property and use it:

self.rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                            moduleName:@"MyApp"
                                     initialProperties:nil
                                         launchOptions:launchOptions];

Now implement the AppDelegate methods for handling the active state like you would do in your iOS apps and pass the information as props:

-(void)onAppActiveStateChanged:(BOOL)appBecameActive
{
  NSMutableDictionary *newProps = [NSMutableDictionary dictionary];
  if (self.rootView.appProperties != nil) {
    [newProps addEntriesFromDictionary:self.rootView.appProperties];
  }
  newProps[@"appDidBecomeActive"] = @(appBecameActive);
  self.rootView.appProperties = newProps;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
  [self onAppActiveStateChanged:NO];
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
  [self onAppActiveStateChanged:YES];
}

On the JavaScript side, do whatever you need when the props change:

componentWillReceiveProps(nextProps) {
  if (nextProps.appDidBecomeActive) {
    alert('app did become active');
  }
}

I'm not sure if this is the best approach, but it's supposed to work...

Upvotes: 1

Mitch
Mitch

Reputation: 1507

I fixed this by using the AppStateIOS API instead of AppState. The later works as expected on iOS devices: when the app goes to the background the "change" event fires, and again when it comes back to the foreground. The AppState API doesn't seem to fire the "change" event at all on an iOS device, as of React Native v0.18, as far as I can tell. The project's conventions suggest that AppState should be a cross-platform wrapper around AppStateIOS, but that doesn't seem to be the case here.

The following example should demonstrate the issue:

React.createClass({

  componentDidMount() {
    AppStateIOS.addEventListener('change', state =>
      console.log('AppStateIOS changed to', state)
    )

    AppState.addEventListener('change', state =>
      console.log('AppState changed to', state)
    )
  },

  render() {
    return <View/>
  }

})

When this component is mounted into a React Native app, you will see "AppStateIOS changed to ..." when closing and opening the app. You will never see "AppState changed to ..." as far as I'm aware.

Update

It appears that this was fixed in React Native recently (as of v26). You can now use AppState as advertised on iOS and Android.

Upvotes: 39

d-vine
d-vine

Reputation: 5547

I think you answered the question your self already. Why don't you use a combination of componentWillMount and AppState -> 'change'? That should cover all cases. I use this for syncing my app via CodePush.

Upvotes: 0

Related Questions