从 SharedPreferences 迁移到 Jetpack DataStore "Java"
Migrate from SharedPreferences to Jetpack DataStore "Java"
根据这个,我正在尝试将当前项目从 SharedPreferences 迁移到 dataStore 以存储用户选择的布局值,问题是读取值,我遇到了 NPE,首先这是我的代码
内部DataStoreRepositoryclass
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));
}
}
我在片段中使用它是这样的
首先我读了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() {
}
});
然后在同一个 class 中,我将值存储在方法 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
的,我试图在像这样的方法
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));
在执行构造函数的代码之前进行评估。因此 dataStore
是 null
并且抛出 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
以检查它是否是第一次启动)。
应用重启
如果每次重新启动应用程序时出现空白屏幕,您可能没有检查片段中当前设置的首选项 onViewCreated
。我个人会在片段中使用 MutableLiveData<String> recyclerViewLayoutPreference
来观察这样的偏好:
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 */
}
});
}
这样你只需要在 changeAndSaveLayout
的情况下调用 dataStoreRepository.saveValue(...)
并且 LiveData
的使用将生命周期事件的必要管理保持在最低限度。
根据这个
内部DataStoreRepositoryclass
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));
}
}
我在片段中使用它是这样的
首先我读了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() {
}
});
然后在同一个 class 中,我将值存储在方法 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
的,我试图在像这样的方法
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));
在执行构造函数的代码之前进行评估。因此 dataStore
是 null
并且抛出 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
以检查它是否是第一次启动)。
应用重启
如果每次重新启动应用程序时出现空白屏幕,您可能没有检查片段中当前设置的首选项 onViewCreated
。我个人会在片段中使用 MutableLiveData<String> recyclerViewLayoutPreference
来观察这样的偏好:
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 */
}
});
}
这样你只需要在 changeAndSaveLayout
的情况下调用 dataStoreRepository.saveValue(...)
并且 LiveData
的使用将生命周期事件的必要管理保持在最低限度。