react-native: `PushNotificationIOS.getInitialNotification` returns null for Local Notifications on Cold Starts.

Handling Local Push Notifications when the app is in the background or inactive state works just fine.

When your app is not running (cold start) and is launched from a Local Push Notification, the practice (according to the current docs) is to use PushNotificationIOS.getInitialNotification. However, this always returns null.

I spent a long time trying to get this to work with getInitialNotification but nothing worked. I had to essentially capture the notification in the launchOptions and then pass it in via appProperties.

My solution is captured on this StackOverflow Answer but here is the crux of it.

AppDelegate.m

// Inside of your didFinishLaunchingWithOptions method...

// Create a Mutable Dictionary to hold the appProperties to pass to React Native.
NSMutableDictionary *appProperties = [NSMutableDictionary dictionary];

if (launchOptions != nil) {
  // Get Local Notification used to launch application.
  UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];

  if (notification) {
    // Instead of passing the entire Notification, we'll pass the userInfo,
    // where a Record ID could be stored, for example.
    NSDictionary *notificationUserInfo = [notification userInfo];

    [ appProperties setObject:notificationUserInfo  forKey:@"initialNotificationUserInfo" ];
  }
}

// Your RCTRootView stuff...

rootView.appProperties = appProperties;

index.ios.js

componentDidMount() {
  if (this.props.initialNotificationUserInfo) {
    console.log("Launched from Notification from Cold State");
    // This is where you could get a Record ID from this.props.initialNotificationUserInfo
    // and redirect to the appropriate page, for example.
  }

  PushNotificationIOS.addEventListener('localNotification', this._onLocalNotification);
}

componentWillUnmount() {
  PushNotificationIOS.removeEventListener('localNotification', this._onLocalNotification);
}

_onLocalNotification( notification ) {
  if (AppState.currentState != 'active') {
    console.log("Launched from Notification from Background or Inactive state.");
  }
  else {
    console.log("Not Launched from Notification");
  }
}

Environment:

  • React Native 0.28
  • iOS Tested Only
  • Local Push Notifications Only
  • Simulator and Real Device Tested
  • Mac El Capitan

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 2
  • Comments: 35 (13 by maintainers)

Most upvoted comments

Right. In Xcode you can set the Debugger to launch when the app launches (like from a notification). Just edit your Scheme and look for this:

screenshot 2016-07-22 13 24 15

And maybe instead of a breakpoint just NSLog out appProperties.

Closing this issue because it has been inactive for a while. If you think it should still be opened let us know why.

Just wanted to leave a +1 for @dwilt & @joshuapinter . Helped me immensely!

@hramos I think we should keep this open until React Native handles this properly in its core. We’re essentially having to use a hack to handle cold start notifications.

Like I mentioned earlier I wouldn’t mind creating a PR to handle this better but wanted to get some input from the React Native core team before spending the effort to do it.

Let me know what you think.

@ericnakagawa we have since moved over to OneSignal for PushNotifications and it’s been a lot easier since so I’m using their guides: https://github.com/geektimecoil/react-native-onesignal

Notice how they’re using pendingNotifications though: https://github.com/geektimecoil/react-native-onesignal#ios-usage

Bingo! That worked. For some reason though, Xcode doesn’t spit out the console stuff at all when using this delayed startup. Really fucking annoying. Had to stringify the object just to see it’s value. Anyway, it’s now on my root app component’s props, as initialNotification:

class GreatJonesStreet extends Component {

    componentDidMount() {
        var { initialNotification } = this.props;

        // initialNotification === {
        //         aps: {
        //             alert: "Check out our new story: \"Dan's Magical Mystery Tour\"!",
        //         },
        //     ^d: "greatjonesstreet://story-detail",
        //     storyId: "2",
        //     storyTitle: "Dan's Magical Mystery Tour",
        //     _: "knsUgFJKlEeaqhNS-2a-UJg"
        // }

        if (initialNotification) {
            AlertIOS.alert(JSON.stringify(initialNotification));

            this._handleNotification(initialNotification);
        }

        PushNotificationIOS.addEventListener('notification', notification => {
            var data = notification.getData();

            if (data) {
                this._handleNotification(data);
            }
        });
    }

    componentWillUnmount() {
        PushNotificationIOS.removeEventListener('notification', this._notificationListener.bind(this));
    }

    _notificationListener(notification) {
        var data = notification.getData();

        if (data) {
            this._handleNotification(data);
        }
    }

    _handleNotification(data) {
        var action = data['^d'] ? data['^d'].split('greatjonesstreet://')[1] : null;

        switch (action) {
            case 'story-detail':
                var title = data.storyTitle,
                    id = parseFloat(data.storyId);

                if (title && id) {
                    this._goToStory(id, title);
                }

                break;

            default:
                break;
        }
    }

    _goToStory(id, title) {
        Actions.StoryDetail({
            id,
            title
        });
    }

    render() {

        const store = configureStore(getInitialState());

        //Connect w/ the Router
        const ConnectedRouter = connect()(Router);

        store.dispatch(setPlatform(platform));
        store.dispatch(setVersion(VERSION));

        // setup the router table with App selected as the initial component
        return (
            <Provider store={store}>
                <ConnectedRouter hideNavBar={true}>
                    <Scene key="root">
                        <Scene key="Home" component={Home} initial={true}/>
                        <Scene key="CollectionDetail" component={CollectionDetail}/>
                        <Scene key="StoryDetail" component={StoryDetail}/>
                    </Scene>
                </ConnectedRouter>
            </Provider>
        );
    }
}

Something to note: The initialNotification doesn’t have the methods getData() and getAlert() like the notification object we get from the PushNotificationIOS.addEventListener method. It’s just a raw object with the data. What would be ideal is if it was the same kind of object so I could just pass both the initial and listener notification objects to the same function. Thanks so much @joshuapinter. Great team work. You’ve increased your karma balance! Now I’m more in debt 😦

Problem is, I’m a JS dude - not an Objective C guy. I’m fucked haha