Skip to content
This repository was archived by the owner on Sep 24, 2021. It is now read-only.

using launch activity resets my app #14

Closed
LuisRodriguezLD opened this issue Feb 29, 2020 · 24 comments
Closed

using launch activity resets my app #14

LuisRodriguezLD opened this issue Feb 29, 2020 · 24 comments

Comments

@LuisRodriguezLD
Copy link

Android:

When the users minimizes the app they immediately get a notification.
If they click on that notification the app won't open unless I set
launchActivity: "default"
but when that happens the app opens up in like new, losing all of my state and router history

This is how I fire the notification:

notifee.displayNotification({
	title: "Pathboks",
	body: "Continue Reading...",
	data: {
		pathbook: pathbook.id,
	},
	android: {
		channelId,
		pressAction: {
			id: "default",
			launchActivity: "default",
		 },
	},
});

And this is how I handle it:

notifee.onBackgroundEvent(async ({ type, detail }) => {
	const pathbook = detail.notification.data.pathbook;
	 if (type === EventType.ACTION_PRESS && pathbook) {
		NavigationService.navigate("Pathbook", { pathbookID: pathbook });
	}
});

If you need to know, I am using React Navigation and the Navigation Service pretty much just takes you to a certain page with parameters.

To summarize:
The notification won't bring up the app unless I set launchActivity but doing so resets the app and loads it fresh.

Suggestion, maybe there is a better way to open the app(?)

@Salakar
Copy link
Contributor

Salakar commented Mar 1, 2020

The notification won't bring up the app unless I set launchActivity

For this part its intentional - as not every action needs to launch an activity, e.g. 'liking a post' action doesn't need to open the app as an example.


In terms of it launching a fresh activity every-time I think this is down to the launch mode we use internally - we'll take a look. (cc @Ehesp I think this is the code in launchPendingIntentActivity thats doing it, I think we might need to also set launch flags on launchIntent too)

@LuisRodriguezLD as part of your app startup (or Activity in this case) you should be calling getInitialNotification to determine what notification (if any) caused your app to open/relaunch. It's best using this along side onBackgroundEvent as the system can recycle backgrounded activities, meaning in that case it'd be a fresh launch anyway, otherwise if it doesn't then your onBackgroundEvent will handle navigation for an already open activity.

@whenmoon
Copy link

whenmoon commented May 31, 2020

This is also happening for me and is undesirable in my case. When my calling app is in the background, I provide users with action buttons to either answer or decline a call on receipt of a notification. Here is my 'Answer' launch activity:

actions: [
          {
            title: 'Answer',
            pressAction: {
              id: 'answer',
              launchActivity: 'default',
            },
          },

However, because the app is in the background, it means the app has already been launched which means the user doesn't need to authenticate and event listeners in the app trigger router actions when an incoming call occurs (which also happens when the app is in the background).

So what I need is for the user to be able to press 'Answer' and the launch action is to bring the app from the background, to the foreground and nothing else. Right now this almost behaves like launchActivity: 'default', kills the app first and then launches it again from scratch, which causes errors to occur as the app state is no longer intact.

What would be ideal in my case would be something like launchActivity: 'bringAppToForeground', which would have exactly exactly the same behaviour as if you were to tap on an icon of an app that was in the background.

I am trying to migrate from custom native Java modules to Notifee for handling notifications and the behaviour I describe is what happens when I create and press a notification using this code:

@ReactMethod
    void createNotification(String notificationMessage, int notificationID, String contentText) {
        ReactApplicationContext context = getReactApplicationContext();

        NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder;

        Resources res = context.getResources();

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {

            String CHANNEL_ID = "channel_ID_0";

            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "channel",
                    NotificationManager.IMPORTANCE_HIGH);
            channel.setDescription("incoming call");
            manager.createNotificationChannel(channel);

            builder = new NotificationCompat.Builder(context, CHANNEL_ID);
        } else {
            builder = new NotificationCompat.Builder(context);
        }

        // Flag indicating that if the described PendingIntent already exists, the
        // current one should be canceled before generating a new one.
        Intent activityIntent = new Intent(context, MainActivity.class);
        activityIntent.putExtra("FromNotification", true);
        PendingIntent action = PendingIntent.getActivity(context, 0, activityIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        builder.setLargeIcon(BitmapFactory.decodeResource(res, R.drawable.ic_stat_icon_))
                .setSmallIcon(R.drawable.ic_stat_icon_).setTicker("Large text!").setAutoCancel(true)
                .setContentTitle(notificationMessage).setPriority(NotificationCompat.PRIORITY_HIGH)
                .setCategory(NotificationCompat.CATEGORY_CALL).setContentText(contentText)
                .setFullScreenIntent(action, true);

        Notification notification = builder.build();

        int notificationCode = notificationID;
        manager.notify(notificationCode, notification);
    }

@Ehesp
Copy link
Member

Ehesp commented Jun 1, 2020

I wonder if we can provide the ability to pass something down which determines the launch behaviour - when building the library out we did have a chat about this, but it would make sense to let the user be in-control.

Maybe something like an ENUM on JS side, which is sent to the intent.

@whenmoon
Copy link

whenmoon commented Jun 1, 2020

This would really help, thanks 👌🏼

@Ehesp
Copy link
Member

Ehesp commented Jun 4, 2020

Hey @whenmoon I've just had a play around, essentially under the hood we create and launch a new Intent when something happens.

Right now, there are no flags on the intent which causes the activity to "relaunch". If I set it to FLAG_ACTIVITY_SINGLE_TOP, the app re-opens without reloading the app.

cc @Salakar I remember we looked into this, can you remember a reason we decided to not use FLAG_ACTIVITY_SINGLE_TOP? From memory, if you specify a custom activity, it meant you'd also lose your current app activity...

@whenmoon
Copy link

whenmoon commented Jun 5, 2020

@Ehesp would you mind providing a code snippet of where to add this flag within the library as a workaround whilst we wait for 'official' support for it? Thanks

@Salakar
Copy link
Contributor

Salakar commented Jun 7, 2020

This should be in a version later today / tomorrow as launchActivityFlags, will post here when up

@Salakar Salakar self-assigned this Jun 7, 2020
@Salakar Salakar closed this as completed in abd8f8f Jun 8, 2020
@Salakar
Copy link
Contributor

Salakar commented Jun 8, 2020

Hey,

launchActivityFlags is live in 0.7.0 with support for all the Activity flags.

Here's an example using SINGLE_TOP;

import { AndroidStyle, AndroidLaunchActivityFlag } from '@notifee/react-native';


const notification = {
  title: 'Testing SINGLE_TOP launch.',
  body: 'Expand for a cat!',
  android: {
    channelId: 'foo',
    pressAction: {
      id: 'default',
      launchActivity: 'default',
      launchActivityFlags: [AndroidLaunchActivityFlag.SINGLE_TOP],
    },
    style: {
      type: AndroidStyle.BIGPICTURE,
      picture: 'https://icatcare.org/app/uploads/2018/07/Thinking-of-getting-a-cat.png',
    },
  },
};

@whenmoon
Copy link

whenmoon commented Jun 8, 2020

Hi @Salakar, would it be possible to provide an example of how to use this where the app is launched / bought to foreground and app state stays intact?

At the moment with this code:

        actions: [
          {
            title: 'Answer',
            pressAction: {
              id: 'answer',
              launchActivity: 'default',
              launchActivityFlags: [AndroidLaunchActivityFlag.SINGLE_TOP],
            },
          },

or using launchActivityFlags: [AndroidLaunchActivityFlag.BROUGHT_TO_FRONT],, app state is destroyed after launch. I have also noticed that in my debugger console I get two instances of this with different rootTags: Running "app" with {"rootTag":1}, one is logged on first launch, then the app is put in to the background, a notification is received, the above action is used and then I get Running "app" with {"rootTag":31}, the app launches but state is reset.

Thanks

@mikehardy
Copy link
Collaborator

Hmm - I can't reproduce, I have it working

https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_SINGLE_TOP is supposed to be the one / https://stackoverflow.com/a/19627387/9910298

I had the same problem as I integrated Notifee just now - no action on tapping a notification I naively popped up with no pressAction set - and after setting it like this, it works:

        APKUpdateService.VERSION_NOTIFICATION_ID = await notifee.displayNotification({
          id: APKUpdateService.VERSION_NOTIFICATION_ID,
          title: I18NService.translate('UpdateAvailableTitle'),
          body: I18NService.translate('UpdateAvailableText'),
          android: {
            channelId: APKUpdateService.UPDATE_NOTIFICATION_CHANNEL_ID,
            smallIcon: 'drawable/minilogo_bw',
            pressAction: {
              id: 'default',
              launchActivity: 'default',
              launchActivityFlags: [AndroidLaunchActivityFlag.SINGLE_TOP],
            },
          },
        });

Maybe you have device developer setting "keep activities" set to false or similar?

@whenmoon
Copy link

whenmoon commented Jun 9, 2020

Hi @mikehardy, hmmm ok thinking about this I would bet that what's stopping it working for me is the fact that I have another notification set up to launch a custom React component.

backgroundIncomingCallNotification.js:

        actions: [
          {
            title: 'Answer',
            pressAction: {
              id: 'answer',
              launchActivity: 'default',
              launchActivityFlags: [AndroidLaunchActivityFlag.SINGLE_TOP],
            },
          },

killedIncomingCallNotification.js:

actions: [
          {
            title: 'Answer',
            pressAction: {
              id: 'answer',
              mainComponent: 'app-router',
            },
          },

Then I obviously have the custom component registered etc as described here

If you register a custom component for another notification does it still work?

The fact that they have the same id doesn't matter right now as they have not been used together yet.

I'll do some proper testing later cheers.

@whenmoon
Copy link

whenmoon commented Jun 9, 2020

Ok I've tried to test this as much as possible and I'm getting very inconsistent results unfortunately. Sometimes on pressing a pressAction button, I get a new Running "app" with {"rootTag":11} logged in the debugger console and the app is launched and the root component is remounted with store state destroyed, and other times I am able to consistently reproduce the following steps:

  1. Launch app
  2. Put app in to background
  3. Receive notification
  4. Notification is displayed by function called from HeadlessJS - without being bought to the foreground the app root is remounted / re-rendered but redux state is left intact: firebase.messaging().setBackgroundMessageHandler(backgroundNotificationHandler)
notifee.onBackgroundEvent(async ({ type, detail }) => {
  const { notification, pressAction } = detail;

  const cancelIncomingCallActions = async () => {
    await notifee.cancelNotification(notification.id);
  }

  if (type === EventType.ACTION_PRESS && pressAction.id === 'answer') {
    store.dispatch(answerCall());
    cancelIncomingCallActions();
  }
});

const backgroundIncomingCallNotification = async (
  notificationTitle,
  notificationBody,
  notificationID,
) => {

  const channelId = await notifee.createChannel({
    id: notificationID,
    name: 'Incoming Call',
    importance: AndroidImportance.HIGH,
    visibility: AndroidVisibility.PUBLIC,
  });

  try {
    await notifee.displayNotification({
      title: `<p style="color: #C9FB5C;"><b>${notificationTitle}</span></p></b></p> &#128075;`,
      description: 'Description',
      android: {
        ongoing: true,
        category: AndroidCategory.CALL,
        channelId,
        importance: AndroidImportance.HIGH,
        visibility: AndroidVisibility.PUBLIC,
        color: COL_BASE_YELLOW,
        vibrationPattern: [300, 500],
        smallIcon: 'ic_stat_icon_',
        style: { type: AndroidStyle.BIGTEXT, text: `${notificationBody}` },
        progress: {
          max: 10,
          current: 5,
          indeterminate: true,
        },
        actions: [
          {
            title: 'Answer',
            pressAction: {
              id: 'answer',
              launchActivity: 'default',
              launchActivityFlags: [AndroidLaunchActivityFlag.SINGLE_TOP],
            },
          },
  1. press pressAction button
  2. Notification is dismissed / app is bought to foreground
  3. Put app in to background
  4. Receive another notification - only thing that happens is backgroundIncomingCallNotification is called
  5. The same notification is displayed
  6. press pressAction button - app is bought to foreground and then app root is remounted / re-rendered but redux state is left intact

If steps 7 - 10 are repeated the the behaviour is consistent - the issue here is that the first time a notification is received, the app is re-mounted in the background before pressing a pressAction button. The second time thereafter the app is only remounted after a pressAction button has been pressed.

The ideal scenario is that the app is never remounted or re-rendered when using a press action. Obviously because the root app component is remounting, functions and so on are being called that you only want to call on first launch, like authentication so I'm having to use flags dispatched to the store to determine how the app has been launched (I would normally do this but am having to do more-so to try and manage this).

Another strange but (I think) linked observation is that when 'reloading' the app from dev tools, I get these stacking up in the console as I mentioned in a comment above:

Running "app" with {"rootTag":31}
Running "app" with {"rootTag":41}
Running "app" with {"rootTag":51}

I also get other logs that I would only expect to see one of - I'm finding it hard to predictably recreate this but it's like the app or some part of it is running more than once at a time if that's even possible.

Some of this could be down to my own logic that needs to be improved upon but one thing that's for sure is that app launch behaviour from press actions is unpredictable for me.

I think I need to move these in to foreground services anyway as I have whole load of async stuff going on in the background.

This is all on physical pixel devices.

@mikehardy
Copy link
Collaborator

Sounds racy, I wonder if you whittled it down to the minimum viable app and made sure it was all the listeners etc were registered in App.js if that would improve things (i.e., you'd win the race consistently)? Without a reproduction it's going to be tough to reason about this, which is just stating the obvious of course. I was doing most of my testing on an emulator, and I'll move to physical devices next. If I notice anything in my implementation i'll mention it but mine is still behaving as expected

@Salakar
Copy link
Contributor

Salakar commented Jun 10, 2020

@whenmoon I've noticed in your snippet above in backgroundIncomingCallNotification you're setting the channel id to the notification id so this would be creating a new channel everytime if these ids are unique, you're also not passing notificationID as the id to the notification that you're creating there, whether this is relevant I'm not sure

@Salakar
Copy link
Contributor

Salakar commented Jun 10, 2020

Could you also try combining single top flag with other flags like clear top, and see what happens

@whenmoon
Copy link

@mikehardy yep ok I'm going to work on this and try to remove anything that could have some kind of effect on it like you mention. @Salakar Whilst doing the above testing I was always using the same notification id every time as I base them on the user sending it so they can be identified but thanks for the observation - I'll fix this, will also try other flag combos and report back.

@whenmoon
Copy link

whenmoon commented Jun 11, 2020

@mikehardy @Salakar I've done a bunch of testing, foreground services, background events, different combinations of launch activity flags, moving listeners in to index.js, App.js, stripping out everything to test functionality one thing at a time, but whatever way I cook it, what I find as consistent, predictable and undesirable behaviour is:

  1. launch app
  2. put in to background
  3. receive notification
  4. app root component is re-mounted, calling all the functions it would do when it launches for the very first time (obviously conditionals based on press actions should dictate what logic is executed when the app opens via a notification press, but this should be defined in state or using getInitialNotification() etc)
  5. press pressAction button
  6. app opens
  7. app put in to the background
  8. receive notification
  9. press pressAction button
  10. app opens
  11. and then app root component is re-mounted (after press, not on notification receipt)

You should be able to test this just by logging inside a function that gets called early on in your application and observing when it gets called.

Then I have this problem which is related to the app registry (this is the line of RN code that handles the info log):

Screenshot 2020-06-11 at 23 07 43

You can see how Notifee is causing multiple calls / renders (launches) in what is in fact a single launch - my knowledge of the app registry is not sufficient to be able to explain exactly what's happening in the background.

The desirable launch action is to bring the app from the background to the foreground (killed app is a slightly different scenario but let's stick to background for now) whilst still being able to hook in to methods / features like getInitialNotification() and EventType, but literally nothing else.

@whenmoon
Copy link

whenmoon commented Jun 14, 2020

I've tested out https://github.com/zo0r/react-native-push-notification to see how it behaves and it does not effect the app in the same way as Notifee when using a press action when the app is in the background. I then modified the library to add setFullScreenIntent to the notification and then it started behaving in the same way as Notifee does. The behaviour I describe in the second half of the comment above always starts a new activity because of the PendingIntent passed to setFullScreenIntent. After changing android:launchMode="singleTop" to android:launchMode="singleTask" in my manifest this behaviour corrected itself - I would guess that the same would be true for Notifee but haven't tested it yet, even though Notifee does not setFullScreenIntent.

@whenmoon
Copy link

whenmoon commented Jun 14, 2020

Ok FYI, this zo0r/react-native-push-notification#1482 solves this issue for me and also this issue. Any chance of similar implementation in Notifee? This would complete the package for me (although as far as I know you're not planning to implement Telecom API / Alarm manager)!

@shubhamverma27
Copy link

Hi,
I am also facing the same issue have tried setting launchActivityFlags: to single top and BROUGHT_TO_FRONT both but every time when my app is in the background and I get a notification, The app just restarts instead of just coming back to screen.
My android launch mode in the manifest is single-task and this is not expected behaviour.
My app notifications are stuck due to this, please help as this is a big issue.

@shubhamverma27
Copy link

Making my handler method async fixed it for me

messaging().setBackgroundMessageHandler(async (remoteMessage) => {

@mikehardy
Copy link
Collaborator

@shubhamverma27 seems like you found a solution? May I assume this is an integration with react-native-firebase? Or perhaps it is an issue with any cloud message library. The reason I ask is the documentation for integration with cloud messaging might need a change to emphasize this more strongly

@shubhamverma27
Copy link

@mikehardy yes, making it async seems to solve the issue.
Yes,I am using it with Rnfirebase messaging and the messaging background handler's promise function had to be made async..You may update that in your documentation :)

@russellwheatley
Copy link
Member

FWIW I've also tested message handling whilst the app is in a background state on Android a number of times, and it never reloaded the app anew.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

7 participants