如何打开关闭应用程序后打开的最后一个片段并使用导航抽屉和导航组件重新打开它

How to open the last fragment opened after closed app and reopen it using Navigation drawer and Navigation Component

更新

由于onSaveInstanceState&onRestoreInstanceState关闭app后无法使用store/restore值,我尝试用dataStore解决,但是不行,这是我的尝试

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();

        }

    }

在 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);
    }

最后是 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();
    }

}

问题详细

在这个应用程序中,我在导航抽屉中有 9 个菜单项和片段,我想将最后打开的片段保存在 savedInstanceStatedatastore 中,并在用户关闭应用程序并重新打开它后再次显示最后打开的片段,但我不知道我将使用哪种方法

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

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>

MainActivityclass

@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();
    }

}

免责声明:

由于 SharedPreference 迟早会被弃用,下面有一个使用 DataStore 的更新。


使用SharedPreference

onSaveInstanceState & onRestoreInstanceState 不能用于 store/restore 应用后的值 closed/shut.

即使不关闭应用程序,也不能依赖它们来存储大对象或长时间存储对象。

您可以使用 SharedPreference 来存储映射到应用程序存在之前最后打开的片段的值。

这里我存储了一些任意值,因为建议不要存储应用程序 ID,因为它们可能因应用程序启动而异。因此,您可以存储任意值并将它们映射到当前应用启动时生成的 ID。

我选择这些值作为数组索引:

// 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
};

然后对于应用程序的每次启动;即在 onCreate() 方法中,您可以从 SharedPreference 中选择当前索引,然后调用 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);
} 

一旦使用 navControllerOnDestinationChangedListener 更改目标,您就可以将新值注册到 sharedPreference:

// 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();
});

将其集成到您的代码中:

@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();
    }

}

使用数据存储

Since I migrated from sharedpreferences to dataStore in this project, I tried to do the same as your solution but it doesn't work, I'll post my try and you can look at it to see what's wrong, then you can edit your answer with dataStore soultion

因此,我将使用相同的方法,但使用 DataStore(相同的片段数组,存储索引而不是片段目标 ID)。

因此,您需要添加片段 ID 数组,以便在 DataStore 进程中读取它:

// Array of fragment IDs
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
};

然后在存储库中更改逻辑以使用索引而不是片段 ID:

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

            Integer fragIndex = preferences.get(CURRENT_DESTINATION);
            if (fragIndex == null) fragIndex = 0;

            if (fragIndex >= 0 && fragIndex <= fragments.length) {
                // Navigate to the fragIndex
                return fragments[fragIndex];
            } else {
                return R.id.nav_home;
            }
        }); 
    
}   

并且在 ViewModel 中,你不应该订阅永久 Observable 到 Flowable 因为这将永久提交对观察到的数据的任何更改,而是你可以转换 FlowableSingle 这样你就可以在应用程序启动时只获得片段 ID 的一个(第一个)值一次,并且不再注册观察者。 Check Documentation了解更多详情。

在您的 ViewModel 中应用它:

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

    dataStoreRepository.readCurrentDestination.firstOrError().subscribeWith(new DisposableSingleObserver<Integer>() {

        @Override
        public void onSuccess(@NotNull Integer destination) {
        
            // Must be run at UI/Main Thread
            runOnUiThread(() -> {
                currentDestination.setValue(destination);
            });
            
        }

        @Override
        public void onError(@NotNull Throwable error) {
            error.printStackTrace();
        }
    }).dispose();

}

然后当您观察 activity 中的 currentDestination MutableLiveData 时:更改那里的当前目的地(您已经做得很好):

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);
});

每当目标更改时将当前片段保存到 DataStore

ViewModel:

public void saveCurrentDestination(int value){
    int fragmentIndex = Arrays.asList(fragments).indexOf(value);
    CURRENT_DESTINATION = PreferencesKeys.intKey(keyName);
    dataStore.updateDataAsync(prefsIn -> {
        MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
        mutablePreferences.set(CURRENT_DESTINATION, fragmentIndex);
        return Single.just(mutablePreferences);
    }).subscribe();

}

如何在导航图中存储当前位置并不重要(我们实际上是在谈论存储单个 long 值以及最终一些参数值)。

先决条件本身应该已经解释了它是如何工作的:

  • 能够使用globalNavAction导航到每个目的地。
  • 能够将存储的目标 ID 解析为 global NavAction
  • 不要忘记导航参数(例如 itemId)。

与此类似,可能会丢失返回堆栈条目,但可以直接导航。如果这还不够,请存储 NavController 后退堆栈条目并播放它们(不建议)。