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

标签 java android android-fragments navigation-drawer android-architecture-navigation

更新
由于onSaveInstanceState & onRestoreInstanceState关闭应用程序后无法用于存储/恢​​复值,我尝试使用 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 个菜单项和 fragment ,我想将最后打开的 fragment 保存在 savedInstanceState 中或 datastore并在用户关闭应用程序并重新打开后再次显示最后打开的 fragment ,但我不知道我将使用哪种方法
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>
MainActivity类
@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 的更新.

使用 SharedPreferenceonSaveInstanceState & onRestoreInstanceState应用程序关闭/关闭后不能用于存储/恢​​复值。
即使应用程序没有关闭,您也不能依赖它们来存储大型对象或长时间存储对象。
取而代之的是,您可以使用 SharedPreference在应用程序存在之前存储一个映射到最后一个打开 fragment 的值。
这里我存储了一些任意值,因为建议不要存储应用程序 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);
} 
一旦目标更改,您可以使用 OnDestinationChangedListener 将新值注册到 sharedPreference 中。的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();
});
将其集成到您的代码中:
@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(相同的 fragment 数组,存储索引而不是 fragment 目标 ID)。
因此,您需要添加 fragment 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
};
然后在存储库中更改逻辑以使用索引而不是 fragment 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 ,你不应该订阅 Flowable 的永久 Observable因为这将永久提交对观察数据的任何更改,但您可以转换 FlowableSingle这样您就只能在应用程序启动时获得 fragment 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();

}
然后当你观察 currentDestination Activity 中的 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);
});
将当前 fragment 保存到 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();

}

关于java - 如何打开关闭应用程序后打开的最后一个 fragment 并使用抽屉导航和导航组件重新打开它,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69511294/

相关文章:

Android - 启动时崩溃

java - 创建 BasicClientCookie

java 我以编程方式下载的文件的大小小于手动下载的文件

java - 程序期间重置全局字符数组的值

java - 空对象引用上的 'java.lang.String android.os.Bundle.getString(java.lang.String)'

android - 服务和 Activity 流程

android - 从一个 fragment 调用另一个 fragment

java - GoogleMap.addMarker 抛出 null 异常

java - 为什么 ArrayList 不覆盖 equals() 以获得更好的性能?

java - 无法选择隐藏链接 - selenium