Java – Launches Fragment on notification click without losing state

Launches Fragment on notification click without losing state… here is a solution to the problem.

Launches Fragment on notification click without losing state

I have a chat app and it is also launched directly with a notification click. Chat fragments can also be started manually by clicking within the app.

I guess if the user hits the home button on the chat fragment and then clicks on the notification, it should start in the last state and don’t call the activity’s onDestroy then onCreate

Start a fragment in an activity like this with a notification.

((AppCompatActivity)context).getFragmentManager().beginTransaction().replace(R.id.Navigation_Main_Layout, screenFragment,"Chat").commit();

I’m handling notifications from FirebaseMessagingService

public class FireBase_Messaging_Service extends FirebaseMessagingService {

public static final String TAG="###FireBase MSG###";
    public static final int NOTIFICATION=5;
    String UserName;
    String ID;
    String Msg;

Map<String,String> data;
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);
        Log.d(TAG,"From "+remoteMessage.getFrom());
        if (remoteMessage.getData().size()>0){
            data = remoteMessage.getData();
            Log.d(TAG,"Message Data "+remoteMessage.getData());
            data = remoteMessage.getData();

UserName = data.get("name");
            ID = data.get("ID");
            Msg = data.get("Message");

showNotification(Msg,ID,UserName);
        }

if (remoteMessage.getNotification()!=null){
            Log.d(TAG,"Message Notification Body "+remoteMessage.getNotification().getBody());
            Toast.makeText(this, "Notification "+remoteMessage.getNotification().getBody(), Toast.LENGTH_LONG).show();
        }
    }

private void showNotification(String Message,String ID,String UserName) {
        Log.d(TAG,"Show Notification "+Message+" "+ID);
        Intent intent=new Intent(this, Navigation_Drawer.class);
        intent.putExtra("Type","Text");
        intent.putExtra("Type",MsgType);
        intent.putExtra("ID",ID);
        intent.putExtra("uname",UserName);
        intent.putExtra("Message",Msg);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent pendingIntent=PendingIntent.getActivity(this,NOTIFICATION,intent,PendingIntent.FLAG_UPDATE_CURRENT);
        int color = getResources().getColor(R.color.black);
        String ChannelID = "Message";
        notificationChannel(ChannelID,"Chat");
        NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(),ChannelID)
                .setSmallIcon(R.drawable.default_x)
                .setColor(color)
                .setContentTitle(UserName)
                .setContentText(Message)
                .setChannelId(ChannelID)
                .setTicker("My App")
                .setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND | Notification.FLAG_SHOW_LIGHTS)
                .setLights(0xff00ff00, 1000, 500) // To change Light Colors
                .setStyle(new NotificationCompat.BigTextStyle().bigText(Message))//For Expandable View
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true);

NotificationManagerCompat managerCompat = NotificationManagerCompat.from(this);
        managerCompat.notify(NOTIFICATION,builder.build());
    }

@Override
    public void onDeletedMessages() {
        super.onDeletedMessages();
    }

private void notificationChannel (String ChannelID, String channelName) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES. O) {
            NotificationChannel  channel = new NotificationChannel(ChannelID,channelName, NotificationManager.IMPORTANCE_DEFAULT);
            channel.setLightColor(Color.GREEN);
            NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(channel);
        }
    }
}

In the above code, I tried intent with different flags, such as Intent.FLAG_ACTIVITY_NEW_TASK, Intent, FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_BROUGHT_TO_ FRONT, FLAG_ACTIVITY_CLEAR_TOP, etc. However, it always calls the onDestroy of the Activity (Navigation Drawer) first, followed by onCreate. It then starts the fragment from scratch.

How can I avoid my app recreating activities and fragments?

Solution

As far as I know, the expected behavior of Android activities conflicts with the Android lifecycle of activities and fragments. When the user presses the back or home button, the activity or fragment goes through onPause and onDestroy following the lifecycle of that activity or fragment instance. You cannot avoid it unless you call finish in an activity to avoid calling the onDestroy function in an activity. However, you do not want to complete the activity, you want to use the same activity, and you do not want to recreate the activity.

So I’m thinking of different ways to solve your problem. In most cases, the problem of recreating an activity or fragment directly indicates the resource to get, and acquiring a large number of resources when initializing the activity or fragment is an overhead. Therefore, we may avoid acquiring resources to be used in activities or fragments when resources are already available in a saved instance state.

For example, when you get some data saved in a SQLite database in an activity’s onCreate function, you may not want to fetch it again when the direction of the activity changes, which forces the activity to be recreated. In this case, you may need to select a loader (I’m talking about the implementation of CursorLoader and LoaderCallbacks) that persists after the activity is recreated. If the data has already been fetched, the loader’s implementation will not fetch the data from the SQLite database again and will provide the data when the activity is recreated.

Another wonderful thing I would like to recommend is using ViewModel. You may find that documentation here explains the implementation. The ViewModel persists after the activity is recreated, and you can use the ViewModel to save the instance state, which will reduce load times.

The point is, you may not be able to spoof lifecycle functions, but you can choose to use a data model, which will survive the recreation of your activities and fragments.

Related Problems and Solutions