You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 04:15:42