59

I am trying to open a fragment when I press a notification in the notification bar. My app structure is:

  • a base activity with a nav drawer menu
  • some fragment that are opened from menu

    b.setOnClickListener(new OnClickListener() {
    
            @SuppressWarnings({ "deprecation", "static-access" })
            public void onClick(View v) {
    
            w_nm=(NotificationManager) getActivity().getSystemService(getActivity().NOTIFICATION_SERVICE);
    
             Notification notify=new Notification(R.drawable.notnificationlogo,waternoti,System.currentTimeMillis());
    
             Intent notificationIntent = new Intent(getActivity(), Abc.class);
    
    
    
             PendingIntent pending=PendingIntent.getActivity(getActivity(), 0,notificationIntent, 0);
    
    
             notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
                     | Intent.FLAG_ACTIVITY_SINGLE_TOP );
    
            notify.flags = Notification.DEFAULT_LIGHTS | Notification.FLAG_AUTO_CANCEL;
    
               notify.setLatestEventInfo(getActivity(),waternoti,waternoti1, pending);
    
             w_nm.notify(0, notify);
    

Can anyone tell me how to link with next fragment page (the present code is in class that extends fragment)

davioooh
  • 23,742
  • 39
  • 159
  • 250

5 Answers5

79

If you are using Navigation Component you can open a specific destination using NavDeepLinkBuilder:


val pendingIntent = NavDeepLinkBuilder(context)
                     .setComponentName(MainActivity::class.java)
                     .setGraph(R.navigation.nav_graph)
                     .setDestination(R.id.destination)
                     .setArguments(bundle)
                     .createPendingIntent()

...

notificationBuilder.setContentIntent(pendingIntent)

...

Please note that it's important to use setComponentName only if your destination isn't in the launcher activity.

arsent
  • 6,975
  • 3
  • 32
  • 31
  • 4
    I am not able to recreate the backstack with this soultion. I have Fragment A -> B -> C -> D -> E when I use this solution the graph is only A -> E . Any idea? – AndroidRuntimeException Dec 05 '19 at 22:11
  • 3
    Note that this opens a new instance of the activity. I spent several hours debugging this. There are other ways to open an existing activity but I'm not sure NavDeepLinkBuilder is the right way – sprajagopal Jun 17 '21 at 20:05
  • [Android Link](https://developer.android.com/guide/navigation/navigation-deep-link) `When a user opens your app via an explicit deep link, the task back stack is cleared and replaced with the deep link destination. When nesting graphs, the start destination from each level of nesting—that is, the start destination from each element in the hierarchy—is also added to the stack.` So you would have to create nested graphs to make your navigation work @AndroidRuntimeException – soan saini Mar 08 '22 at 07:10
47

You will need to start your base activity as usual, but add some extra info to the intent about what menu fragment will be opened. Here you can see how it can be done: https://stackoverflow.com/a/8610916/1652236

This depends on the extra information which you retrieve in the activities 'onCreate()' method in which you will use to start/load the fragment.

See here for example how work with fragments: http://www.tutorialspoint.com/android/android_fragments.htm http://developer.android.com/guide/components/fragments.html

It Intent to launch this procedure will be something like:

Intent notificationIntent = new Intent(getActivity(), Abc.class);
notificationIntent.putExtra("menuFragment", "favoritesMenuItem");

and in your base activity:

@Override
protected void onCreate(final Bundle savedInstanceState)
{
    String menuFragment = getIntent().getStringExtra("menuFragment");

    FragmentManager fragmentManager = getFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

    // If menuFragment is defined, then this activity was launched with a fragment selection
    if (menuFragment != null) {

        // Here we can decide what do to -- perhaps load other parameters from the intent extras such as IDs, etc
        if (menuFragment.equals("favoritesMenuItem")) {
            FavoritesFragment favoritesFragment = new FavoritesFragment();
            fragmentTransaction.replace(android.R.id.content, favoritesFragment);
         }
    } else {
         // Activity was not launched with a menuFragment selected -- continue as if this activity was opened from a launcher (for example)
         StandardFragment standardFragment = new StandardFragment();
         fragmentTransaction.replace(android.R.id.content, standardFragment);
    }
}
Community
  • 1
  • 1
Vito
  • 1,414
  • 1
  • 13
  • 22
  • Here in this app i need to generate my own message as notification and i kept that button in one of the menu option on the actionbar, i used only one activity and rest all are fragments – Venkat Ramarao Potlapalli Oct 28 '14 at 13:00
  • As I understood you, you need to start your base activity with some fragment. And fragment type must depends on notification type. Is it true? – Vito Oct 28 '14 at 13:02
  • Not all fragments, only few fragment pages should depend on notification type – Venkat Ramarao Potlapalli Oct 28 '14 at 13:09
  • Maybe I don't fully understand your question. Can you please post code of your base activity, that I can help? – Vito Oct 28 '14 at 13:15
  • Ok I will explain it now, i have an activity with grid-view, then after click on any icon in the grid-view, next pages are fragments. i have 16 icons and their respective fragment pages. I have kept a button in one of the menu option on the actionbar, so whenever i click it self notification is generated. – Venkat Ramarao Potlapalli Oct 28 '14 at 13:22
  • I understood that. But whole structure still is not so clear for me. For example, is grid view in fragment xml or in activity xml? also are these fragments in different activity or in the same as grid view? – Vito Oct 28 '14 at 13:39
  • not work ... for me and I can't put in oncreate (not regenerate) and always get null I put in onResume ... I repeat I CAN'T PUT ONCREATE! –  Oct 16 '15 at 16:10
  • how can i maintain backstack in this case? As i want to return back to home screen after user taps on notification and fragments get opened – Vivek Singh Jul 05 '16 at 10:32
  • If you have got ViewModels in your Fragment, you don't want to have the LiveData destroyed by recreating the Activity – Marvin Oct 12 '20 at 22:34
9
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
             | Intent.FLAG_ACTIVITY_SINGLE_TOP )

As your intent set Flags: FLAG_ACTIVITY_SINGLE_TOP, "onCreate()" will not be called when the activity has been created, you should receive the params in the method called "onNewIntent()" instead.

gustavo
  • 141
  • 1
  • 10
2

you should also add .commit(); and ft1.addToBackStack(null); so that it would not overlap on prevoius one and if you will not ad this ft1.addToBackStack(null); on back your app will exit so add this according to your functionality

String menuFragment = getIntent().getStringExtra("menuFragment");

ft1 = getSupportFragmentManager().beginTransaction();

ft1.addToBackStack(null);

ft1.replace(R.id.frame_container, favoritesFragment).commit();
Anik Islam Abhi
  • 25,137
  • 8
  • 58
  • 80
Jishant
  • 574
  • 3
  • 14
0

It is a detailed answer and i believe if you follow it carefully. You will solve your problem.

When you send the notification from Firebase this methods runs first

public class FirebaseMessageService extends FirebaseMessagingService {

private static final String TAG = "MyFirebaseMsgService";
Map<String, String> data;
// [START receive_message]
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {

    if (remoteMessage.getData().size() > 0) {
        removeUserInfoByKeyAsStatic(getApplicationContext(), USER_KNOW_CREATED_EVENT_NOTIFICATION_DATA);
        Log.d(TAG, "Message data payload: " + remoteMessage.getData());
        data = remoteMessage.getData();


        HashMap<String, String> copyData = new HashMap<>(data);

        //Calling method to generate notification
        sendNotification(data.get("body"), data.get("title"), copyData);
    }

}

then you run the above sendNotification(..) method. In this method i set the notification data to the SharedPref. Dont be offensive about it., we will remove it later immediately when we use it.

I used my login data and set intent to diffrent Activity according to my roleID

private void sendNotification(String messageBody, String messageTitle, HashMap<String, String> data) {

    // this methods checks my notification data is null ? isNullOrEmptyExt : "simple java is null control method"
    if (!isNullOrEmptyExt(data.get("data"))) { 

        Intent intent;
        final User myInfos = getMyInfos(getApplicationContext()); // this gives me login user data.
        boolean isConsumerOrVolunteer = myInfos.getRoleID() == Constants.ROLES.CONSUMER || myInfos.getRoleID() == Constants.ROLES.VOLUNTEER;
        // I want to show here. You can select diffrent Activity with your data..
        if (isConsumerOrVolunteer) {
            // this set my notification data to SharedPref
            setUserInfoByKeyAsStatic(getApplicationContext(), USER_KNOW_CREATED_EVENT_NOTIFICATION_DATA, data.get("data"));
            intent = new Intent(this, EventListActivity.class);
        } else {
            intent = new Intent(this, ProducerActivity.class);
        }

        // after from here are the generally same. tricks above
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT);

        String channelId = getString(R.string.default_notification_channel_id);
        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder =
                new NotificationCompat.Builder(this, channelId)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setContentTitle(messageTitle)
                        .setContentText(messageBody)
                        .setAutoCancel(true)
                        .setSound(defaultSoundUri)
                        .setContentIntent(pendingIntent);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // Since android Oreo notification channel is needed.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());

    }

}

When you send notification this above two methods runs. So after that you will click the notification on your phone.. right.. This click operation opens the Intent(ActivityName) that you set before in sendNotification(..) method.

E.g you click the notification and your ProducerActivity or EventListActivity opens..

If your Role is Consumer or Volunteer ( according to my app roles ) you set the notification data to SharedPref

   boolean isConsumerOrVolunteer = myInfos.getRoleID() == Constants.ROLES.CONSUMER || myInfos.getRoleID() == Constants.ROLES.VOLUNTEER;
    // I want to show here. You can select diffrent Activity with your data..
    if (isConsumerOrVolunteer) {
        // this set my notification data to SharedPref
        setUserInfoByKeyAsStatic(getApplicationContext(), USER_KNOW_CREATED_EVENT_NOTIFICATION_DATA, data.get("data"));
        intent = new Intent(this, EventListActivity.class);
    } else {
        intent = new Intent(this, ProducerActivity.class);
    }

So you opened the EventListActivity. Lets see what will we do in EventListActivity

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_event_list_main);

    GsonBuilder gsonBuilder = new GsonBuilder();
    gson = gsonBuilder.create();

    // this gives me data from `SharePref`
    String notificationEventDataString = getUserInfoByKeyAsStatic(this, USER_KNOW_CREATED_EVENT_NOTIFICATION_DATA);

    // then check the datastring that comes from SharedPref - simple java null check method
    if(!isNullOrEmptyExt(notificationEventDataString)){
        Bundle bundle = new Bundle();
        bundle.putParcelable(EVENT, gson.fromJson(notificationEventDataString, Event.class));
        removeUserInfoByKeyAsStatic(this, USER_KNOW_CREATED_EVENT_NOTIFICATION_DATA);
        openFragmentWithoutAnimation(this, R.id.drawer_layout, bundle, EventPreviewFragment.class, EVENT_PREVIEW_FRAGMENT);
    }

You get the data from SharedPref . This will not be empty because when you get the notfication we already set it in sendNotification(..) method .

So you put your data to bundle and open Fragment with bundle data. Right after you remove the SharedPref data that includes my notification data

I'm ALSO SHARING MY CUSTOM METHODS WITH YOU

This one is fragment open method

public static void openFragmentWithoutAnimation(Context context, int replaceLayout, Bundle bundle, Class<?> fragment, String fragmentTag) {
    Fragment dynamicFragment = null;
    try {
        Class<?> clazz = Class.forName(fragment.getName());
        dynamicFragment = (Fragment) clazz.newInstance();
        System.out.println(clazz.getSuperclass());
        System.out.println(clazz.getName());
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

    AppCompatActivity activity = null;
    if (context instanceof AppCompatActivity) {
        activity = ((AppCompatActivity) context);
    }
    if (activity.getSupportFragmentManager().findFragmentByTag(fragmentTag) == null) {
        FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
        if (bundle != null) {
            dynamicFragment.setArguments(bundle);
        }
        transaction.add(replaceLayout, dynamicFragment, fragmentTag);
        transaction.addToBackStack(fragmentTag);
        transaction.commit();
    }
}

These are SharedPref set and get methods

public static void setUserInfoByKeyAsStatic(Context context, String key, String value){
    final SharedPreferences prefs = context.getSharedPreferences(
            USER_INFO_PREFS_KEY, Context.MODE_PRIVATE);// Saved token can be accessed only from this app or other apps that shared same id with this app.
    SharedPreferences.Editor editor = prefs.edit();
    editor.putString(key, value);
    editor.apply();
}

public static String getUserInfoByKeyAsStatic(Context context, String key){
    final SharedPreferences controlPrefs = context.getSharedPreferences(
            USER_INFO_PREFS_KEY, Context.MODE_PRIVATE);
    return controlPrefs.getString(key,null);
}

And theese are the null check methods

public static boolean isNullOrEmpty(String value){

    if(value == null){
        return true;
    }else return value.isEmpty() || value.equals("null");

}

public static boolean isNullOrEmptyExt(String value){

    if(value == null){
        return true;
    }else return value.isEmpty() || value.equals("null") || value.equals(JSON_ARRAY_EMPTY) || value.equals(JSON_OBJECT_EMPTY);

}
Farukest
  • 1,498
  • 21
  • 39