从Preferences DataStore读取语言和位置数据返回Null求助
Android DataStore读取数据返回Null导致语言选择状态不保存问题
我开发的Android应用要实现切换语言时更改文本的功能,但现在遇到数据读取问题:从Preferences DataStore读取"language"和"position"数据时返回Null。
具体场景:点击底部弹窗(Bottom Sheet)里的单选按钮后退出,再次打开弹窗时,之前选中的语言没被记住,单选按钮处于未选中状态,排查后发现是从偏好设置读取对应数据时返回Null。我是Android开发新手,试过用Observe但语法不对,希望能帮忙排查问题,相关代码如下:
Favourite Language Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val binding = FavouriteBottomSheetBinding.bind(view) // 从PreferenceManager读取数据返回Null val getFavouriteLanguage = viewModel.onFavouriteLanguage.value // 从PreferenceManager读取数据返回Null val getFavouritePosition = viewModel.onFavouritePosition.value val favouriteAdapter = FavouriteAdapter(this, getFavouriteLanguage, getFavouritePosition) binding.apply { favouriteLanguageList.apply { adapter = favouriteAdapter layoutManager = LinearLayoutManager(requireContext()) } } viewModel.favouriteLanguage.observe(viewLifecycleOwner) { favouriteAdapter.submitList(it) } } override fun onFavouriteLanguageClick(selectedFavouriteLanguage: String, position: Int) { viewModel.onSelectedFavouriteLanguage(selectedFavouriteLanguage) viewModel.onSelectedFavouritePosition(position) d("favouritebuttonclick", "$selectedFavouriteLanguage, $position") } }
Favourite Language View Model
@HiltViewModel class FavouriteViewModel @Inject constructor( languageDao: LanguageDao, private val preferencesManager: PreferencesManager ) : ViewModel() { val favouriteLanguage = languageDao.getFavouriteLanguageByName().asLiveData() // 尝试从偏好管理器读取语言和位置数据 val onFavouriteLanguage = preferencesManager.favouriteLanguageFlow.asLiveData() val onFavouritePosition = preferencesManager.favouritePositionFlow.asLiveData() fun onSelectedFavouriteLanguage(selectedFavouriteLanguage: String) = viewModelScope.launch { d("viewmodelvariable", selectedFavouriteLanguage) preferencesManager.updateSelectedFavouriteLanguage(selectedFavouriteLanguage) } fun onSelectedFavouritePosition(selectedFavouritePosition: Int) = viewModelScope.launch { d("viewmodelvariable", "$selectedFavouritePosition") preferencesManager.updateSelectedFavouritePosition(selectedFavouritePosition) } }
Preferences Manager
@Singleton class PreferencesManager @Inject constructor(@ApplicationContext context: Context) { private val dataStore = context.dataStore val preferencesFlow = dataStore.data .catch { exception -> if (exception is IOException) { Log.e(TAG, "Error reading preferences", exception) emit(emptyPreferences()) } else { throw exception } } .map { preferences -> val sortOrder = SortOrder.valueOf( preferences[PreferencesKeys.SORT_ORDER] ?: SortOrder.BY_NAME.name ) val hideSelectedLanguage = preferences[PreferencesKeys.HIDE_SELECTED_LANGUAGE] ?: false FilterPreferences(sortOrder, hideSelectedLanguage) } suspend fun updateSortOrder(sortOrder: SortOrder) { dataStore.edit { preferences -> preferences[PreferencesKeys.SORT_ORDER] = sortOrder.name } } suspend fun updateHideSelectedLanguage(hideSelectedLanguage: Boolean) { dataStore.edit { preferences -> preferences[PreferencesKeys.HIDE_SELECTED_LANGUAGE] = hideSelectedLanguage } } suspend fun updateSelectedFavouriteLanguage(selectedFavouriteLanguage: String) { Log.d("preferencevariable", selectedFavouriteLanguage) dataStore.edit { preferences -> preferences[PreferencesKeys.SELECTED_FAVOURITE_LANGUAGE] = selectedFavouriteLanguage } } suspend fun updateSelectedFavouritePosition(selectedFavouritePosition: Int) { Log.d("preferencevariable", selectedFavouritePosition.toString()) dataStore.edit { preferences -> preferences[PreferencesKeys.SELECTED_FAVOURITE_POSITION] = selectedFavouritePosition } } // 尝试从datastore读取语言 val favouriteLanguageFlow: Flow<String?> = dataStore.data .catch { exception -> if (exception is IOException) { Log.e(TAG, "Error while trying to read user preferences", exception) emit(emptyPreferences()) } else { throw exception } } .map { preferences -> preferences[PreferencesKeys.SELECTED_FAVOURITE_LANGUAGE] } // 尝试从datastore读取语言位置 val favouritePositionFlow: Flow<Int?> = dataStore.data .catch { exception -> if (exception is IOException) { Log.e(TAG, "Error while trying to read user preferences", exception) emit(emptyPreferences()) } else { throw exception } } .map { preferences -> preferences[PreferencesKeys.SELECTED_FAVOURITE_POSITION] } private object PreferencesKeys { val SORT_ORDER = stringPreferencesKey("sort_order") val HIDE_SELECTED_LANGUAGE = booleanPreferencesKey("hide_selected_language") val SELECTED_FAVOURITE_LANGUAGE = stringPreferencesKey("selected_favourite_language") val SELECTED_FAVOURITE_POSITION = intPreferencesKey("selected_favourite_position") }
Favourite Language Adapter
class FavouriteAdapter( private val listener: OnFavouriteLanguageClickListener, favouriteLanguage: String?, favouritePosition: Int? ) : ListAdapter<Language, FavouriteAdapter.FavouriteViewAHolder>(DiffCallback()) { private var selectFavouritePosition = favouritePosition private var selectLanguage = favouriteLanguage override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavouriteViewAHolder { val binding = ItemFavouriteBinding.inflate(LayoutInflater.from(parent.context), parent, false) return FavouriteViewAHolder(binding) } override fun onBindViewHolder(holder: FavouriteViewAHolder, position: Int) { val currentItem = getItem(position) holder.bind(currentItem, position, selectFavouritePosition, selectLanguage) holder.favouriteLanguageRadioButton.setOnClickListener { d("testbutton", "show test") selectFavouritePosition = holder.adapterPosition selectLanguage = currentItem.language d("holderlistener", "$selectLanguage, $selectFavouritePosition") listener.onFavouriteLanguageClick(selectLanguage!!, selectFavouritePosition!!) notifyDataSetChanged() } } inner class FavouriteViewAHolder(private val binding: ItemFavouriteBinding) : RecyclerView.ViewHolder(binding.root) { val favouriteLanguageRadioButton = binding.rbIsClicked fun bind( language: Language, position: Int, selectFavouritePosition: Int?, selectLanguage: String? ) { binding.apply { tvFavouriteLanguage.text = language.language d("selectFavourite", "$selectFavouritePosition") d("selectFavourite", "$selectLanguage") if (selectFavouritePosition == -1 && position == 0) { favouriteLanguageRadioButton.isChecked = false } else if (selectFavouritePosition != position && selectLanguage != language.language) { favouriteLanguageRadioButton.isChecked = false } else if (selectFavouritePosition == position && selectLanguage == language.language) { favouriteLanguageRadioButton.isChecked = true } } } } interface OnFavouriteLanguageClickListener { fun onFavouriteLanguageClick(selectedFavouriteLanguage: String, position: Int) } class DiffCallback : DiffUtil.ItemCallback<Language>() { override fun areItemsTheSame(oldItem: Language, newItem: Language) = oldItem.id == newItem.id override fun areContentsTheSame(oldItem: Language, newItem: Language) = oldItem == newItem } }
问题修复方案
1. 解决LiveData直接取value的异步问题
LiveData是异步数据流,初始化阶段直接调用.value会因为数据还未加载完成返回Null,必须通过observe监听数据变化:
修改FavouriteLanguageFragment的onViewCreated方法:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val binding = FavouriteBottomSheetBinding.bind(view) // 先初始化空状态适配器 val favouriteAdapter = FavouriteAdapter(this, null, null) binding.apply { favouriteLanguageList.apply { adapter = favouriteAdapter layoutManager = LinearLayoutManager(requireContext()) } } // 观察语言列表数据 viewModel.favouriteLanguage.observe(viewLifecycleOwner) { favouriteAdapter.submitList(it) } // 监听保存的选中语言,更新适配器状态 viewModel.onFavouriteLanguage.observe(viewLifecycleOwner) { selectedLang -> favouriteAdapter.updateSelectedState(selectedLang, viewModel.onFavouritePosition.value) } // 监听保存的选中位置,更新适配器状态 viewModel.onFavouritePosition.observe(viewLifecycleOwner) { selectedPos -> favouriteAdapter.updateSelectedState(viewModel.onFavouriteLanguage.value, selectedPos) } }
2. 给适配器添加动态更新选中状态的方法
原适配器只在初始化时接收选中状态,后续数据变化无法同步,需要添加更新方法:
修改FavouriteAdapter:
class FavouriteAdapter( private val listener: OnFavouriteLanguageClickListener, favouriteLanguage: String?, favouritePosition: Int? ) : ListAdapter<Language, FavouriteAdapter.FavouriteViewAHolder>(DiffCallback()) { private var selectFavouritePosition = favouritePosition private var selectLanguage = favouriteLanguage // 添加更新选中状态的方法 fun updateSelectedState(lang: String?, pos: Int?) { selectLanguage = lang selectFavouritePosition = pos notifyDataSetChanged() } // 其余代码保持不变... }
3. 可选:添加默认值处理首次打开场景
如果用户从未选择过语言,DataStore会返回Null,可给Flow设置默认值:
修改PreferencesManager的Flow定义:
// 读取语言,默认返回第一个语言(根据你的实际数据修改默认值) val favouriteLanguageFlow: Flow<String> = dataStore.data .catch { exception -> if (exception is IOException) { Log.e(TAG, "Error while trying to read user preferences", exception) emit(emptyPreferences()) } else { throw exception } } .map { preferences -> preferences[PreferencesKeys.SELECTED_FAVOURITE_LANGUAGE] ?: "English" } // 读取位置,默认选中第0项 val favouritePositionFlow: Flow<Int> = dataStore.data .catch { exception -> if (exception is IOException) { Log.e(TAG, "Error while trying to read user preferences", exception) emit(emptyPreferences()) } else { throw exception } } .map { preferences -> preferences[PreferencesKeys.SELECTED_FAVOURITE_POSITION] ?: 0 }
同时ViewModel里的LiveData类型改为非空:
val onFavouriteLanguage = preferencesManager.favouriteLanguageFlow.asLiveData() val onFavouritePosition = preferencesManager.favouritePositionFlow.asLiveData()
4. 验证DataStore写入是否正常
检查updateSelectedFavouriteLanguage和updateSelectedFavouritePosition方法中的日志输出,确认数据是否成功写入DataStore。如果写入正常,通过上述监听逻辑就能解决读取时的Null问题。
内容的提问来源于stack exchange,提问作者Spidey
相关产品推荐
相关产品推荐

