braze-inc / braze-react-native-sdk

Public repo for the Braze React Native SDK
https://www.braze.com
Other
64 stars 84 forks source link

Android push notification deep link from background restarting app #55

Closed sin2 closed 5 years ago

sin2 commented 5 years ago

I have a detached expo project set up with this SDK. I am having issues where opening an Android app from tapping a push notification with a deep link causes the android app to restart/open from the splash screen. This leads to the listener from not firing, and getInitialUrl() just returns the base deep link.

This only occurs when the application was backgrounded. From a killed state, when a push notification is opened the application starts and uses getInitialUrl(). It deep links appropriately in this case.

The project was configured according to the Braze docs and mainActivity launchmode is set to singleTask.


Seems like the issue is specifically caused by the intent set for the notification when there is a deep link. AppboyNotificationUtils.setContentIntentIfPresent(context, notificationBuilder, notificationExtras);

I was able to simply deep link using: Intent pushActionIntent = (new Intent(Intent.ACTION_VIEW, Uri.parse("app://friends"))).setClass(context, MainActivity.class); However, this would bypass the default AppboyFcmReceiver and the logic contained there.

Bucimis commented 5 years ago

Hey @sin2,

Good digging. That setContentIntentIfPresent routes an intent back to our BroadcastReceiver, allowing it to log analytics before opening the deep link in the intent.

When we open deep links, we use a standard ACTION_VIEW, however, it lives inside a stackbuilder that places the main activity on the background before navigating to the deep link URI. The code lives here: https://github.com/Appboy/android-sdk/blob/develop/android-sdk-ui/src/main/java/com/appboy/ui/actions/UriAction.java#L142

That's probably what's triggering your splash activity. You can use our back stack configurations settings added in 2.1.4 to change or disable our default stackbuilder behavior - https://github.com/Appboy/appboy-android-sdk/blob/master/CHANGELOG.md#214

sin2 commented 5 years ago

Setting <bool name="com_appboy_push_deep_link_back_stack_activity_enabled">false</bool> does not seem to work. Also looks like it is opening the correct activity.

Here is a partial log:

02-19 10:26:55.911 21720-21720/com.app.app D/Appboy v2.7.0 .com.appboy.AppboyFcmReceiver: Received broadcast message. Message: Intent { act=com.appboy.action.APPBOY_PUSH_CLICKED flg=0x10 cmp=com.app.app/com.appboy.AppboyFcmReceiver bqHint=4 VirtualScreenParam=Params{mDisplayId=-1, null, mFlags=0x00000000)} (has extras) }
02-19 10:26:55.911 21720-21720/com.app.app D/Appboy v2.7.0 .com.appboy.push.AppboyNotificationUtils: Sending notification opened broadcast
02-19 10:26:55.911 21720-22163/com.app.app D/Appboy v2.7.0 .com.appboy.Appboy: No campaign Id associated with this notification. Not logging push click to Appboy.
02-19 10:26:55.931 21720-21720/com.app.app D/Appboy v2.7.0 .com.appboy.configuration.CachedConfigurationProvider: Defaulting to using xml value for key: com_appboy_handle_push_deep_links_automatically and value: true
02-19 10:26:55.931 21720-21720/com.app.app D/Appboy v2.7.0 .com.appboy.push.AppboyNotificationUtils: Found a deep link app://friends
    Use webview set to: false
02-19 10:26:55.941 21720-21720/com.app.app D/Appboy v2.7.0 .com.appboy.ui.actions.UriAction: Executing Uri action from channel PUSH: app://friends. UseWebView: false. Extras: Bundle[{source=Appboy, ab_use_webview=false, cid=null, uri=app://friends}]
02-19 10:26:55.941 21720-21720/com.app.app D/Appboy v2.7.0 .com.appboy.configuration.CachedConfigurationProvider: Defaulting to using xml value for key: com_appboy_push_deep_link_back_stack_activity_enabled and value: false
02-19 10:26:55.941 21720-21720/com.app.app D/Appboy v2.7.0 .com.appboy.ui.actions.UriAction: Not adding back stack activity while opening uri from push due to disabled configuration setting.
02-19 10:26:56.341 21720-22163/com.app.app D/Appboy v2.7.0 .bo.app.dr: No stored open session in storage.
02-19 10:26:56.341 21720-22163/com.app.app D/Appboy v2.7.0 .bo.app.ac: bo.app.ao fired: bo.app.ao@b8e680b
02-19 10:26:56.341 21720-22163/com.app.app D/Appboy v2.7.0 .bo.app.ac: Triggering bo.app.ao on 1 subscribers.
02-19 10:26:56.341 21720-22163/com.app.app D/Appboy v2.7.0 .bo.app.bn: New session created with ID: 39754c1a-3bb1-4b26-8922-516804548798
02-19 10:26:56.341 21720-21720/com.app.app D/Appboy v2.7.0 .com.appboy.ui.inappmessage.AppboyInAppMessageManager: registerInAppMessageManager called
02-19 10:26:56.341 21720-22163/com.app.app D/Appboy v2.7.0 .bo.app.ac: bo.app.aq fired: bo.app.aq@3c77994
    Triggering bo.app.aq on 1 subscribers.
02-19 10:26:56.341 21720-22163/com.app.app D/Appboy v2.7.0 .bo.app.bl: Completed the openSession call. Starting or continuing session 39754c1a-3bb1-4b26-8922-516804548798
02-19 10:26:56.341 21720-22163/com.app.app D/Appboy v2.7.0 .bo.app.bm: Messaging session timeout: 21600, current diff: 26
    Messaging session not started.
02-19 10:26:56.341 21720-22163/com.app.app V/Appboy v2.7.0 .bo.app.bl: Opened session with activity: host.exp.exponent.MainActivity

The activity is setup in the manifest like so:

    <activity
      android:name=".MainActivity"
      android:launchMode="singleTask"
      android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
      android:theme="@style/Theme.Exponent.Splash"
      android:windowSoftInputMode="adjustResize">
      <intent-filter>
        <data android:scheme="app" />
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
      </intent-filter>
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>
Bucimis commented 5 years ago

@sin2 thanks for sharing - the logs show expected behavior and this manifest entry look good so the issue is likely on the React side. Could you share your getInitialUrl/componentDidMount implementation?

sin2 commented 5 years ago

I have a listener in componentDidMount:

 Linking.addEventListener('url', this.handleOpenURL);

  handleOpenURL(event) {
    openDeepLink(event.url);
  }

Then in my splash component I handle the deep link after data is loaded:

const url = await Linking.getInitialURL();
      if (url) {
        openDeepLink(url);
      }

      ReactAppboy.getInitialURL(pushNotificationURL => {
        if (pushNotificationURL) {
          openDeepLink(pushNotificationURL);
        }
      });
sin2 commented 5 years ago

Also I can successfully deep link using adb shell am start -W -a android.intent.action.VIEW -d "app://friends" Which leads me to believe it is implemented correctly.

Bucimis commented 5 years ago

Hey @sin2, we were still not able to repro the behavior on our end. Our sample app's getInitialUrl implementation worked whether the app was in the background or killed (see https://github.com/Appboy/appboy-react-sdk/blob/master/AppboyProject/AppboyProject.js#L66). Note that our sample calls getInitialURL from directly within componentDidMount - and that we don't use a splash activity. You might also see what behavior you get if you temporarily remove your splash activity.

Separately, could you provide us with either a sample app that reproduces the behavior or a minimum set of changes to our sample app that we can implement to repro the behavior? Short of that, a set of logs for each case with relevant lifecycle events in react (e.g. componentDidMount, getInitialURL+its url, _handleOpenUrl) and Braze's verbose SDK logs would be helpful to understand what's going on.

sin2 commented 5 years ago

@Bucimis I have created a sample expo detached project to reproduce the issue. https://github.com/sin2/expo-braze-test

Bucimis commented 5 years ago

@sin2 thanks for sending over the sample app, we were able to repro on our end as well. We've narrowed the issue to an interaction between the FLAG_ACTIVITY_NEW_TASK and Linking.getInitialURL on singleTask apps - in short, our push intent doesn't have the flag (and doesn't work) and the adb flag includes the flag (and works). The following is code you can add to MainActivity's onCreate() method to force Appboy to use FLAG_ACTIVITY_NEW_TASK for push intents. We'll also explore adding FLAG_ACTIVITY_NEW_TASK to our base SDK and let you know if that happens.

After adding the following we were able to get deep linking working in all situations:

// imports
import com.appboy.enums.Channel;
import com.appboy.IAppboyNavigator;
import com.appboy.ui.AppboyNavigator;
import com.appboy.ui.actions.NewsfeedAction;
import com.appboy.ui.actions.UriAction;
        AppboyNavigator.setAppboyNavigator(new IAppboyNavigator() {
            @Override
            public void gotoNewsFeed(Context context, NewsfeedAction newsfeedAction) {
                new AppboyNavigator().gotoNewsFeed(context, newsfeedAction);
            }

            @Override
            public void gotoUri(Context context, UriAction uriAction) {
                if (uriAction.getChannel().equals(Channel.PUSH)) {
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setData(uriAction.getUri());

                    if (uriAction.getExtras() != null) {
                        intent.putExtras(uriAction.getExtras());
                    }
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                    if (intent.resolveActivity(context.getPackageManager()) != null) {
                        context.startActivity(intent);
                    }
                } else {
                    new AppboyNavigator().gotoUri(context, uriAction);
                }
            }
        });

Let us know how this goes on your end. Thanks!

sin2 commented 5 years ago

@Bucimis it's working great! Thanks your time and work, and we will keep an eye out for changes to the base SDK.

comanzanares commented 5 years ago

hey @Bucimis I had deep link not working when app was in foreground and when app was killed. The code you shared fixes the foreground case but when app is killed and I tap on a notification, deep link is not working. Any idea or hint where to look at?

Bucimis commented 5 years ago

@comanzanares i know the original ticket is android, but just want to sanity check - are you seeing this on android, ios or both?

comanzanares commented 5 years ago

Android

Bucimis commented 5 years ago

Hi @comanzanares thanks for following up.

Do you see the same behaviors when using the following adb command to test your deep link in the closed state:

adb shell am start -W -a android.intent.action.VIEW -d "THE_DEEP_LINK" THE_PACKAGE_NAME

Our deep link code essentially runs the above command in code, so if the adb command doesn't work, deep links will also not work.

comanzanares commented 5 years ago

Hey @Bucimis thanks for reaching out so fast, I get the exact same behaviour with adb :/

Bucimis commented 5 years ago

@comanzanares are you calling Linking.getInitialURL() in your componentDidMount() per https://github.com/Appboy/appboy-react-sdk/blob/5887ece50db2099bc4c54ff6a0d55e0a6f8842c2/AppboyProject/AppboyProject.js#L58? Also, make sure deep links are properly configured on the native side per https://developer.android.com/training/app-links/deep-linking.

comanzanares commented 5 years ago

yup @Bucimis the code was skipping the getInitialUrl for some conditions. thanks!

Daphne-CoffeeIT commented 2 years ago

AppboyNavigator is now deprecated and the following should be used:

BrazeDeeplinkHandler.setBrazeDeeplinkHandler(object : IBrazeDeeplinkHandler {
    override fun gotoNewsFeed(context: Context?, newsfeedAction: NewsfeedAction?) {

    }

    override fun gotoUri(context: Context?, uriAction: UriAction?) {

    }

    override fun getIntentFlags(intentFlagPurpose: IBrazeDeeplinkHandler.IntentFlagPurpose?): Int {

    }

    override fun createUriActionFromUrlString(
        url: String?,
        extras: Bundle?,
        openInWebView: Boolean,
        channel: Channel?
    ): UriAction {

    }

    override fun createUriActionFromUri(
        uri: Uri?,
        extras: Bundle?,
        openInWebView: Boolean,
        channel: Channel?
    ): UriAction {

    }
 })