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).