Home > Net >  How to open the last fragment opened after closed app and reopen it using Navigation drawer and Navi
How to open the last fragment opened after closed app and reopen it using Navigation drawer and Navi

Time:10-17

Update

Since onSaveInstanceState & onRestoreInstanceState can't be used to store/restore values after closed the app, I tried to use dataStore to solve it, but it dosen't work, here's my trying

DataStoreRepository

@ActivityRetainedScoped
    public static class DataStoreRepository {
        RxDataStore<Preferences> dataStore;

        public static Preferences.Key<Integer> CURRENT_DESTINATION =
                PreferencesKeys.intKey("CURRENT_DESTINATION");

        public final Flowable<Integer> readCurrentDestination;

        @Inject
        public DataStoreRepository(@ApplicationContext Context context) {
            dataStore =
                    new RxPreferenceDataStoreBuilder(Objects.requireNonNull(context), /*name=*/ "settings").build();

            readCurrentDestination = dataStore.data().map(preferences -> {
                if (preferences.get(CURRENT_DESTINATION) != null) {
                    return preferences.get(CURRENT_DESTINATION);
                } else {
                    return R.id.nav_home;
                }

            });
        }


        public void saveCurrentDestination(String keyName, int value){

            CURRENT_DESTINATION = PreferencesKeys.intKey(keyName);
            dataStore.updateDataAsync(prefsIn -> {
                MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
                Integer currentKey = prefsIn.get(CURRENT_DESTINATION);

                if (currentKey == null) {
                    saveCurrentDestination(keyName,value);
                }

                mutablePreferences.set(CURRENT_DESTINATION,
                        currentKey != null ? value : R.id.nav_home);
                return Single.just(mutablePreferences);
            }).subscribe();

        }

    }

read and save in ViewModel

public final MutableLiveData<Integer> currentDestination = new MutableLiveData<>();


 @Inject
    public PostViewModel(Repository repository, Utils.DataStoreRepository dataStoreRepository) {
        this.repository = repository;
        getAllItemsFromDataBase = repository.localDataSource.getAllItems();
        this.dataStoreRepository = dataStoreRepository;

        dataStoreRepository.readCurrentDestination
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new FlowableSubscriber<Integer>() {
                    @Override
                    public void onSubscribe(@NonNull Subscription s) {
                        s.request(Long.MAX_VALUE);
                    }

                    @Override
                    public void onNext(Integer integer) {

                    }

                    @Override
                    public void onError(Throwable t) {
                        Log.e(TAG, "onError: "   t.getMessage());
                    }

                    @Override
                    public void onComplete() {

                    }
                });

    }

    public void saveCurrentDestination(int currentDestination) {
        dataStoreRepository
                .saveCurrentDestination("CURRENT_DESTINATION", currentDestination);
    }

and finally MainActivity

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    @SuppressWarnings("unused")
    private AppBarConfiguration mAppBarConfiguration;
    private NavHostFragment navHostFragment;
    private  NavController navController;
    NavGraph navGraph;
    private PostViewModel postViewModel;



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        postViewModel = new ViewModelProvider(this).get(PostViewModel.class);


        setSupportActionBar(binding.appBarMain.toolbar);
        mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_home, R.id.nav_accessory,
                R.id.nav_arcade, R.id.nav_fashion,
                R.id.nav_food, R.id.nav_heath,
                R.id.nav_lifestyle, R.id.nav_sports, R.id.about)
                .setOpenableLayout(binding.drawerLayout)
                .build();


        navHostFragment = (NavHostFragment)
                getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);

        if(navHostFragment !=null) {
          navController = navHostFragment.getNavController();
        }

        NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
        NavigationUI.setupWithNavController(binding.navView, navController);

        navGraph = navController.getNavInflater().inflate(R.navigation.mobile_navigation);

        postViewModel.currentDestination.observe(this,currentDestination -> {
            Log.d(TAG, "currentDestination: "   currentDestination);
            Toast.makeText(this,"currentDestination"   currentDestination,Toast.LENGTH_SHORT).show();
            navGraph.setStartDestination(currentDestination);
            navController.setGraph(navGraph);
        });

        navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
            Log.d(TAG, "addOnDestinationChangedListener: "   destination.getId());
            postViewModel.saveCurrentDestination(destination.getId());
        });



    }


    @Override
    public boolean onSupportNavigateUp() {
        return NavigationUI.navigateUp(navController, mAppBarConfiguration)
                || super.onSupportNavigateUp();
    }

}

Problem in detail

In this app I have 9 menu items and fragments in navigation drawer, I want to save the last opened fragment in savedInstanceState or datastore and after the user closed the app and re open it again display the last opend fragment, but I don't know which method I'll use

Navigation.findNavController(activity,nav_graph).navigate();

or

binding.navView.setNavigationItemSelectedListener(item -> false);

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@ id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <include
        android:id="@ id/app_bar_main"
        layout="@layout/app_bar_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.google.android.material.navigation.NavigationView
        android:id="@ id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        android:background="@color/color_navigation_list_background"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/activity_main_drawer" />

</androidx.drawerlayout.widget.DrawerLayout>

activity_main_drawer.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="navigation_view">

    <group android:checkableBehavior="single">
        <item
            android:id="@ id/nav_home"
            android:title="@string/home"
            android:icon="@drawable/home"
            />

        <item
            android:id="@ id/nav_accessory"
            android:title="@string/accessory"
            android:icon="@drawable/necklace"
            />
        <item
            android:id="@ id/nav_arcade"
            android:title="@string/arcade"
            android:icon="@drawable/arcade_cabinet"
            />
        <item
            android:id="@ id/nav_fashion"
            android:title="@string/fashion"
            android:icon="@drawable/fashion_trend"
            />
        <item
            android:id="@ id/nav_food"
            android:title="@string/food"
            android:icon="@drawable/hamburger"
            />
        <item
            android:id="@ id/nav_heath"
            android:title="@string/heath"
            android:icon="@drawable/clinic"
            />
        <item
            android:id="@ id/nav_lifestyle"
            android:title="@string/lifestyle"
            android:icon="@drawable/yoga"
            />
        <item
            android:id="@ id/nav_sports"
            android:title="@string/sports"
            android:icon="@drawable/soccer"
            />

        <item
            android:id="@ id/nav_favorites"
            android:title="@string/favorites_posts"
            android:icon="@drawable/ic_favorite"
            />

        <item
            android:id="@ id/about"
            android:title="@string/about"
            android:icon="@drawable/about"
            />

    </group>

</menu>

nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@ id/mobile_navigation"
    app:startDestination="@id/nav_home">

    <fragment
        android:id="@ id/nav_home"
        android:name="com.blogspot.abtallaldigital.ui.HomeFragment"
        android:label="@string/home"
        tools:layout="@layout/fragment_home">
        <action
            android:id="@ id/action_nav_home_to_detailsFragment"
            app:destination="@id/detailsFragment"
            app:popUpTo="@id/nav_home" />
    </fragment>

    <fragment
        android:id="@ id/nav_accessory"
        android:name="com.blogspot.abtallaldigital.ui.AccessoryFragment"
        android:label="@string/accessory"
        tools:layout="@layout/fragment_accessory" >
        <action
            android:id="@ id/action_nav_Accessory_to_detailsFragment"
            app:destination="@id/detailsFragment" />
    </fragment>

    <fragment
        android:id="@ id/nav_arcade"
        android:name="com.blogspot.abtallaldigital.ui.ArcadeFragment"
        android:label="@string/arcade"
        tools:layout="@layout/fragment_arcade" >
        <action
            android:id="@ id/action_nav_Arcade_to_detailsFragment"
            app:destination="@id/detailsFragment" />
    </fragment>

    <fragment
        android:id="@ id/nav_fashion"
        android:name="com.blogspot.abtallaldigital.ui.FashionFragment"
        android:label="@string/fashion"
        tools:layout="@layout/fragment_fashion" >
        <action
            android:id="@ id/action_nav_Fashion_to_detailsFragment"
            app:destination="@id/detailsFragment" />
    </fragment>
    <fragment
        android:id="@ id/nav_food"
        android:name="com.blogspot.abtallaldigital.ui.FoodFragment"
        android:label="@string/food"
        tools:layout="@layout/food_fragment" >
        <action
            android:id="@ id/action_nav_Food_to_detailsFragment"
            app:destination="@id/detailsFragment" />
    </fragment>
    <fragment
        android:id="@ id/nav_heath"
        android:name="com.blogspot.abtallaldigital.ui.HeathFragment"
        android:label="@string/heath"
        tools:layout="@layout/heath_fragment" >
        <action
            android:id="@ id/action_nav_Heath_to_detailsFragment"
            app:destination="@id/detailsFragment" />
    </fragment>
    <fragment
        android:id="@ id/nav_lifestyle"
        android:name="com.blogspot.abtallaldigital.ui.LifestyleFragment"
        android:label="@string/lifestyle"
        tools:layout="@layout/lifestyle_fragment" >
        <action
            android:id="@ id/action_nav_Lifestyle_to_detailsFragment"
            app:destination="@id/detailsFragment" />
    </fragment>
    <fragment
        android:id="@ id/nav_sports"
        android:name="com.blogspot.abtallaldigital.ui.SportsFragment"
        android:label="@string/sports"
        tools:layout="@layout/sports_fragment" >
        <action
            android:id="@ id/action_nav_Sports_to_detailsFragment"
            app:destination="@id/detailsFragment" />
    </fragment>
    <dialog
        android:id="@ id/about"
        android:name="com.blogspot.abtallaldigital.ui.AboutFragment"
        android:label="about"
        tools:layout="@layout/about" />
    <fragment
        android:id="@ id/detailsFragment"
        android:name="com.blogspot.abtallaldigital.ui.DetailsFragment"
        android:label="Post details"
        tools:layout="@layout/fragment_details" >
        <argument
            android:name="postItem"
            app:argType="com.blogspot.abtallaldigital.pojo.Item" />
    </fragment>
    <fragment
        android:id="@ id/nav_favorites"
        android:name="com.blogspot.abtallaldigital.ui.FavoritesFragment"
        android:label="Favorites posts"
        tools:layout="@layout/fragment_favorites" >
        <action
            android:id="@ id/action_favoritesFragment_to_detailsFragment"
            app:destination="@id/detailsFragment" />
    </fragment>
</navigation>

MainActivity class

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {

    @SuppressWarnings("unused")
    private AppBarConfiguration mAppBarConfiguration;
    public static Utils.DataStoreRepository DATA_STORE_REPOSITORY;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());


        setSupportActionBar(binding.appBarMain.toolbar);
        mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_home, R.id.nav_accessory,
                R.id.nav_arcade, R.id.nav_fashion,
                R.id.nav_food, R.id.nav_heath,
                R.id.nav_lifestyle, R.id.nav_sports, R.id.about)
                .setOpenableLayout(binding.drawerLayout)
                .build();


        NavHostFragment navHostFragment = (NavHostFragment)
                getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);

        assert navHostFragment != null;
        NavController navController = navHostFragment.getNavController();

        NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
        NavigationUI.setupWithNavController(binding.navView, navController);


    }


    @Override
    public boolean onSupportNavigateUp() {
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        return NavigationUI.navigateUp(navController, mAppBarConfiguration)
                || super.onSupportNavigateUp();
    }

}

CodePudding user response:

onSaveInstanceState & onRestoreInstanceState can't be used to store/restore values after the app is closed/shut.

Even if the app is not closed, you can't rely on them for storing large objects or storing objects for a long time.

Instead of that you can use SharedPreference to store a value that maps to last open fragment before the app exists.

Here I store some arbitrary value, as it's recommended not to store application IDs, as they can vary from app launch to another. So, you can store arbitrary values and map them to the generated IDs in the current app launch.

I picked those values as array indices:

// Array of fragments
private Integer[] fragments = {
    R.id.nav_home, 
    R.id.nav_accessory,
    R.id.nav_arcade, 
    R.id.nav_fashion,
    R.id.nav_food, 
    R.id.nav_heath,
    R.id.nav_lifestyle, 
    R.id.nav_sports, 
    R.id.about
};

Then for every launch of the app; i.e. in onCreate() method, you can pick the current index from the SharedPreference, and call graph.setStartDestination():

// Getting the last fragment:
SharedPreferences mSharedPrefs = getSharedPreferences("SHARED_PREFS", MODE_PRIVATE);
int fragIndex = mSharedPrefs.getInt(LAST_FRAGMENT, -1); // The last fragment index 
// Check if it's a valid index
if (fragIndex >= 0 && fragIndex < fragments.length) {
    // Navigate to this fragment
    int currentFragment = fragments[fragIndex];
    graph.setStartDestination(currentFragment);

    // Change the current navGraph
    navController.setGraph(graph);
} 

And you can register new values to the sharedPreference once the destination is changed using OnDestinationChangedListener of the navController:

// Listener to the change in fragments, so that we can updated the shared preference
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
    int fragmentIndex = Arrays.asList(fragments).indexOf(destination.getId());
    SharedPreferences.Editor editor = mSharedPrefs.edit();
    editor.putInt(LAST_FRAGMENT, fragmentIndex).apply();
});

Integrating this into your code with:

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    @SuppressWarnings("unused")
    private AppBarConfiguration mAppBarConfiguration;
    private NavHostFragment navHostFragment;
    private  NavController navController;
    NavGraph navGraph;

    
    // Array of fragments
    private Integer[] fragments = {
        R.id.nav_home, 
        R.id.nav_accessory,
        R.id.nav_arcade, 
        R.id.nav_fashion,
        R.id.nav_food, 
        R.id.nav_heath,
        R.id.nav_lifestyle, 
        R.id.nav_sports, 
        R.id.about
    };
    
    // Key for saving the last fragment in the Shared Preferences
    private static final String LAST_FRAGMENT = "LAST_FRAGMENT";
    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());


        setSupportActionBar(binding.appBarMain.toolbar);
        mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_home, R.id.nav_accessory,
                R.id.nav_arcade, R.id.nav_fashion,
                R.id.nav_food, R.id.nav_heath,
                R.id.nav_lifestyle, R.id.nav_sports, R.id.about)
                .setOpenableLayout(binding.drawerLayout)
                .build();


        navHostFragment = (NavHostFragment)
                getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);

        if(navHostFragment !=null) {
          navController = navHostFragment.getNavController();
        }

        NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
        NavigationUI.setupWithNavController(binding.navView, navController);

        navGraph = navController.getNavInflater().inflate(R.navigation.mobile_navigation);

        
        // Getting the last fragment:
        SharedPreferences mSharedPrefs = getSharedPreferences("SHARED_PREFS", MODE_PRIVATE);
        int fragIndex = mSharedPrefs.getInt(LAST_FRAGMENT, -1); // The last fragment index 
        // Check if it's a valid index
        if (fragIndex >= 0 && fragIndex < fragments.length) {
            // Navigate to this fragment
            int currentFragment = fragments[fragIndex];
            graph.setStartDestination(currentFragment);

            // Change the current navGraph
            navController.setGraph(graph);
        } 
        

        
        // Listener to the change in fragments, so that we can updated the shared preference
        navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
            int fragmentIndex = Arrays.asList(fragments).indexOf(destination.getId());
            SharedPreferences.Editor editor = mSharedPrefs.edit();
            editor.putInt(LAST_FRAGMENT, fragmentIndex).apply();
        });
        

    }


    @Override
    public boolean onSupportNavigateUp() {
        return NavigationUI.navigateUp(navController, mAppBarConfiguration)
                || super.onSupportNavigateUp();
    }

}

CodePudding user response:

How you store the current location within the navigation graph doesn't matter much (we are actually talking about storing a single one long value and eventually some argument values).

The preconditions by themselves should already explain how it works:

  • being able to navigate to each destination with a global NavAction.
  • being able to resolve the stored destination ID to a global NavAction.
  • not to forget about navigation arguments (eg. alike an itemId).

Alike this one may loose the back-stack entries, but one can navigate directly. If this shouldn't suffice, store the NavController back-stack entries and them play them back (not suggested).

  • Related