Android 上的 Kotlin:如何在片段中使用数据库中的 LiveData?

Posted

技术标签:

【中文标题】Android 上的 Kotlin:如何在片段中使用数据库中的 LiveData?【英文标题】:Kotlin on Android: How to use LiveData from a database in a fragment? 【发布时间】:2021-12-19 10:55:47 【问题描述】:

我使用 MVVM 并在数据库中有一个数据元素列表,该列表通过 DAO 和存储库映射到 ViewModel 函数。

现在,我的问题相当平庸;我只想使用片段变量中的数据,但我得到类型不匹配。

MVVM 引入了一些代码,为了上下文的完整性,我将运行它,但我将把它剥离到基本部分:

数据元素属于数据类“对象”:

@Entity(tableName = "objects")
data class Objects(
    @ColumnInfo(name = "object_name")
    var objectName: String
) 
    @PrimaryKey(autoGenerate = true)
    var id: Int? = null

在 ObjectsDao.kt 中:

@Dao
interface ObjectsDao 
    @Query("SELECT * FROM objects")
    fun getObjects(): LiveData<List<Objects>>

我的数据库:

@Database(
    entities = [Objects::class],
    version = 1
)
abstract class ObjectsDatabase: RoomDatabase() 
    abstract fun getObjectsDao(): ObjectsDao
    companion object 
        // create database
    

在 ObjectsRepository.kt 中:

class ObjectsRepository (private val db: ObjectsDatabase) 
    fun getObjects() = db.getObjectsDao().getObjects()

在 ObjectsViewModel.kt 中:

class ObjectsViewModel(private val repository: ObjectsRepository): ViewModel() 
    fun getObjects() = repository.getObjects()

在 ObjectsFragment.kt 中:

class ObjectsFragment : Fragment(), KodeinAware 

    private lateinit var viewModel: ObjectsViewModel

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) 
        super.onViewCreated(view, savedInstanceState)

        viewModel = ViewModelProvider(this, factory).get(ObjectsViewModel::class.java)

        // I use the objects in a recyclerview; rvObjectList
        rvObjectList.layoutManager = GridLayoutManager(context, gridColumns)
        val adapter = ObjectsAdapter(listOf(), viewModel)

        // And I use an observer to keep the recyclerview updated
        viewModel.getObjects.observe(viewLifecycleOwner, 
            adapter.objects = it
            adapter.notifyDataSetChanged()
        )
    

适配器:

class ObjectsAdapter(var objects: List<Objects>,
                     private val viewModel: ObjectsViewModel):
    RecyclerView.Adapter<ObjectsAdapter.ObjectsViewHolder>() 
// Just a recyclerview adapter

现在,以上所有操作都很好——但我的问题是我不想使用观察者来填充回收视图;在数据库中我存储了一些对象,但还有更多我不想存储的对象。

所以,我尝试这样做(在 ObjectsFragment 中):

var otherObjects: List<Objects>
// ...
if (condition) 
    adapter.objects = viewModel.getObjects()
 else 
    adapter.objects = otherObjects

adapter.notifyDataSetChanged()

最后,我的问题;我得到真实条件分配的类型不匹配:

类型不匹配:推断类型为 LiveData 但应为 List

我无法理解这一点。这不是观察者身上发生的事情吗?我知道支持属性,例如解释 here,但是当我的数据未在 ViewModel 中定义时,我不知道该怎么做。

【问题讨论】:

您想根据条件过滤从数据库返回的列表。对吗? 不,我有时想从数据库中填充recyclerview,有时我想用其他一些数据填充它。例如,有时我想查看我保存在数据库中的收藏文件,有时我想查看我从文件系统中读取的所有文件;是同一个recyclerview,但是我从不同的地方获取数据。 这是一次性的事情还是可以改变?我的意思是,您是否决定了列表一次并且在应用程序上线之前不更改它,或者您是否在这些列表之间切换? 我切换 - 通常通过切换切换按钮;有时您想查看收藏夹,有时您想查看所有内容。 【参考方案1】:

我们需要一些东西来切换数据源。我们将切换数据源事件传递给 viewModel。

mySwitch.setOnCheckedChangeListener  _, isChecked ->
    viewModel.switchDataSource(isChecked)

在 viewModel 中我们处理切换数据源

(使用 switchMap 包括 implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"

class ObjectsViewModel(private val repository: ObjectsRepository) : ViewModel() 
    // Best practice is to keep your data in viewModel. And it is useful for us in this case too.
    private val otherObjects = listOf<Objects>()
    private val _loadDataFromDataBase = MutableLiveData<Boolean>()

    // In case your repository returns liveData of favorite list
    // from dataBase replace MutableLiveData(otherObjects) with repository.getFavorite()
    fun getObjects() = _loadDataFromDataBase.switchMap 
        if (it) repository.getObjects() else MutableLiveData(otherObjects)
    

    fun switchDataSource(fromDataBase: Boolean) 
        _loadDataFromDataBase.value = fromDataBase
    


在活动/片段中观察getObjects()

viewModel.getObjects.observe(viewLifecycleOwner, 
    adapter.objects = it
    adapter.notifyDataSetChanged()
)

【讨论】:

【参考方案2】:

你可以这样做:

var displayDataFromDatabase = true // Choose whatever default fits your use-case

var databaseList = emptyList<Objects>() // List we get from database
val otherList = // The other list that you want to show

toggleSwitch.setOnCheckedChangeListener  _, isChecked -> 
    displayDataFromDatabase = isChecked // Or the negation of this
    // Update adapter to use databaseList or otherList depending upon "isChecked"


viewModel.getObjects.observe(viewLifecycleOwner)  list ->
    databaseList = list
    if(displayDataFromDatabase)
        // Update adapter to use this databaseList


【讨论】:

您还可以将这些新变量移动到您的 ViewModel 中,以便它们在配置更改后继续存在。 我尝试了,但我觉得我有什么问题?首先,我必须为 databaseList (= emptyList()) 指定类型,并且由于 getObjects 不接受参数,因此我收到有关参数过多的错误。列表参数也需要指定类型。并且 val 无法重新分配 :-) 我正在尝试对其进行修改以避免错误,但这不会最终将 LiveData 分配给 List 吗? 1.是的,您需要指定列表的类型。 2. 我的错,我输入了viewModel.getObjects(viewLifeCyclerOwner) 而不是viewModel.getObjects.observe(viewLifecycleOwner)。 3.databaseListvar。 4. 不,我们没有将 LiveData 分配给 List,我们将实时数据(这是一个列表)的值分配给 databaseList

以上是关于Android 上的 Kotlin:如何在片段中使用数据库中的 LiveData?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Kotlin 从 Android 中的片段访问另一个片段?

Kotlin Android Studio - setContenView - 绑定(片段)

如何在 kotlin 中使 Material Button ToggleGroup 的所有子项不可点击

Kotlin:找不到符号类片段或其他 android 类

Android (Kotlin) - 导航操作取决于可见的片段视图

通过适配器在片段之间传递数据(Kotlin Android)