java - 从 SharedPreferences 迁移到 Jetpack DataStore "Java"

标签 java android nullpointerexception rx-java android-jetpack-datastore

根据这个question ,我试图将当前项目从 SharedPreferences 迁移到 dataStore 以存储用户选择的布局值,问题是读取该值,我得到了 NPE,首先这是我的代码

内部DataStoreRepository

public class Utils {

/*
* Some unrelated codes
*/

public static class DataStoreRepository {
        RxDataStore<Preferences> dataStore;

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


        public Preferences.Key<String> RECYCLER_VIEW_LAYOUT_KEY;


        public void saveValue(String keyName, String value) {

            RECYCLER_VIEW_LAYOUT_KEY = PreferencesKeys.stringKey(keyName);

            dataStore.updateDataAsync(prefsIn -> {
                MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
                String currentKey = prefsIn.get(RECYCLER_VIEW_LAYOUT_KEY);

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

                mutablePreferences.set(RECYCLER_VIEW_LAYOUT_KEY,
                        currentKey != null ? value : "cardLayout");
                return Single.just(mutablePreferences);
            }).subscribe();
           // The update is completed once updateResult is completed.
        }

        public Flowable<String> readLayoutFlow =
                dataStore.data().map(prefs -> prefs.get(RECYCLER_VIEW_LAYOUT_KEY));

    }
}

我在这样的 fragment 中使用它

首先我读了 flowable

private Utils.DataStoreRepository dataStoreRepository;
private String layout2;


public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {


        dataStoreRepository = new Utils.DataStoreRepository(requireContext());

            dataStoreRepository.readLayoutFlow.subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new FlowableSubscriber<String>() {
                        @Override
                        public void onSubscribe(@io.reactivex.rxjava3.annotations.NonNull Subscription s) {

                        }

                        @Override
                        public void onNext(String layout) {
                            layout2 = layout;
                        }

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

                        @Override
                        public void onComplete() {

                        }
                    });

然后在同一个类中,我将值存储在方法 changeAndSaveLayout()

private void changeAndSaveLayout() {
        android.app.AlertDialog.Builder builder
                = new android.app.AlertDialog.Builder(getContext());

        builder.setTitle(getString(R.string.choose_layout));

        String[] recyclerViewLayouts = getResources().getStringArray(R.array.RecyclerViewLayouts);
//        SharedPreferences.Editor editor = sharedPreferences.edit();


        builder.setItems(recyclerViewLayouts, (dialog, index) -> {
            switch (index) {
                case 0: // Card List Layout
                    adapter.setViewType(0);
                    binding.homeRecyclerView.setLayoutManager(layoutManager);
                    binding.homeRecyclerView.setAdapter(adapter);
//                    editor.putString("recyclerViewLayout", "cardLayout");
//                    editor.apply();

                    dataStoreRepository.saveValue("recyclerViewLayout","cardLayout");

                    break;
                case 1: // Cards Magazine Layout
                    adapter.setViewType(1);
                    binding.homeRecyclerView.setLayoutManager(layoutManager);
                    binding.homeRecyclerView.setAdapter(adapter);
//                    editor.putString("recyclerViewLayout", "cardMagazineLayout");
//                    editor.apply();
                    dataStoreRepository.saveValue("recyclerViewLayout","cardMagazineLayout");
                    break;
                case 2: // PostTitle Layout
                    adapter.setViewType(2);
                    binding.homeRecyclerView.setLayoutManager(titleLayoutManager);
                    binding.homeRecyclerView.setAdapter(adapter);
//                    editor.putString("recyclerViewLayout", "titleLayout");
//                    editor.apply();
                    dataStoreRepository.saveValue("recyclerViewLayout","titleLayout");
                    break;
                case 3: //Grid Layout
                    adapter.setViewType(3);
                    binding.homeRecyclerView.setLayoutManager(gridLayoutManager);
                    binding.homeRecyclerView.setAdapter(adapter);
//                    editor.putString("recyclerViewLayout", "gridLayout");
//                    editor.apply();
                    dataStoreRepository.saveValue("recyclerViewLayout","gridLayout");

            }
        });

        android.app.AlertDialog alertDialog = builder.create();
        alertDialog.show();
    }

当我运行时,我得到了这个 NPE,它是关于读取值 readLayoutFlow ,我尝试在方法之外创建 key ,如下所示

public Preferences.Key<String> RECYCLER_VIEW_LAYOUT_KEY = PreferencesKeys.stringKey("recyclerViewLayout");

并在 saveValue() 中像这样更改它的值,但它也不起作用

RECYCLER_VIEW_LAYOUT_KEY.to(keyName);

NPE的输出

 Process: com.blogspot.abtallaldigital, PID: 9184
    java.lang.NullPointerException: Attempt to invoke virtual method 'io.reactivex.rxjava3.core.Flowable androidx.datastore.rxjava3.RxDataStore.data()' on a null object reference
        at com.blogspot.abtallaldigital.utils.Utils$DataStoreRepository.<init>(Utils.java:4)
        at com.blogspot.abtallaldigital.ui.home.HomeFragment.onCreateView(HomeFragment.java:10)
        at androidx.fragment.app.Fragment.performCreateView(Fragment.java:4)
        at androidx.fragment.app.FragmentStateManager.f(FragmentStateManager.java:15)
        at androidx.fragment.app.FragmentStateManager.l(FragmentStateManager.java:20)
        at androidx.fragment.app.FragmentStore.s(FragmentStore.java:3)
        at androidx.fragment.app.FragmentManager.h0(FragmentManager.java:6)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3)
        at androidx.fragment.app.FragmentManager.G(FragmentManager.java:1)
        at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:2)
        at androidx.fragment.app.FragmentStateManager.f(FragmentStateManager.java:26)
        at androidx.fragment.app.FragmentStateManager.l(FragmentStateManager.java:20)
        at androidx.fragment.app.FragmentStore.s(FragmentStore.java:3)
        at androidx.fragment.app.FragmentManager.h0(FragmentManager.java:6)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3)
        at androidx.fragment.app.FragmentManager.m(FragmentManager.java:4)
        at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:1)
        at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:6)
        at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:1)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1435)
        at android.app.Activity.performStart(Activity.java:8018)
        at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3475)
        at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
        at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

最佳答案

出现NullPointerException的原因是readLayoutFlow的初始化之前dataStore .
代码

public Flowable<String> readLayoutFlow =
                dataStore.data().map(prefs -> prefs.get(RECYCLER_VIEW_LAYOUT_KEY));

在执行构造函数的代码之前进行评估。因此dataStorenull并抛出 NullPointerException。

一个简单的解决方案是移动 readLayoutFlow 的初始化像这样进入构造函数:

public static class DataStoreRepository {
    RxDataStore<Preferences> dataStore;
    
    public final Flowable<String> readLayoutFlow;

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

        readLayoutFlow = dataStore.data()
            .map(prefs -> prefs.get(RECYCLER_VIEW_LAYOUT_KEY));
    }

    \\ other code
}

编辑:首次启动时出现空白屏幕

您可能指的是两种不同的情况。我将回答这两个问题:

安装应用后首次启动

要在第一次启动时设置布局,我建议为第一次启动时保存的首选项设置一个默认值(设置 booleanPreferencesKey 以检查是否是第一次启动)。

重新启动应用

如果应用程序每次新启动时都出现黑屏,则您可能没有检查 fragment onViewCreated 中当前设置的首选项。就我个人而言,我会使用 MutableLiveData<String> recyclerViewLayoutPreference在 fragment 中观察偏好,如下所示:

private MutableLiveData<String> recyclerViewLayoutPreference;

@Override
public void onCreate(/*params*/) {
    super.onCreate(/*params*/);
    recyclerViewLayoutPreferences = new MutableLiveData<>();
}

@Override
public void onViewCreated(/*params*/) {
    super.onViewCreated(/*params*/),

    dataStoreRepository.readLayoutFlow()
        .subscribeOn(Schedulers.io())
        .doOnNext(recyclerViewLayoutPreference::postValue)
        .subscribe();

    recyclerViewLayoutPreference.observe(getViewLifecycleOwner(), layoutString -> {
        if (layoutString == null) return;

        switch (layoutString) {
            case "cardLayout":
                adapter.setViewType(0);
                binding.homeRecyclerView.setLayoutManager(layoutManager);
                    binding.homeRecyclerView.setAdapter(adapter);
            /* other cases */
        }
    });
}

这样您只需调用 dataStoreRepository.saveValue(...)changeAndSaveLayout 的情况下以及 LiveData 的用法将生命周期事件的必要管理保持在最低限度。

关于java - 从 SharedPreferences 迁移到 Jetpack DataStore "Java",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69180313/

相关文章:

java - for 循环 [数组] 期间精度损失

java - 为 Java 应用程序设置全局字体

java - 使用queryRaw()自动加载ForeignCollection

android - inputStream.read() 导致 NullPointerException(检查 inputStream 后!=null)

java - 将 java 中的加密代码转换为 Ruby

java - 防止 Java XML Web 服务中的枚举

android - 当通过导航组件的深层链接打开 fragment 而不是返回到起始目标 fragment 时,在后退按钮上关闭应用程序

java - 无法从 WifiP2pDeviceList 获取设备

java - 什么是NullPointerException,我该如何解决?

swing - 调用 super() JDialog 时的 Powermock,Mockito nullpointerexception