Home > Back-end >  Kotlin on Android: How to use LiveData from a database in a fragment?
Kotlin on Android: How to use LiveData from a database in a fragment?

Time:11-06

I use MVVM and have a list of data elements in a database that is mapped through a DAO and repository to ViewModel functions.

Now, my problem is rather banal; I just want to use the data in fragment variables, but I get a type mismatch.

The MVVM introduces a bit of code, and for completeness of context I'll run through it, but I'll strip it to the essentials:

The data elements are of a data class, "Objects":

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

In ObjectsDao.kt:

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

My database:

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

In ObjectsRepository.kt:

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

In ObjectsViewModel.kt:

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

In 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()
        })
    }
}

The adapter:

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

Now, all the above works fine - but my problem is that I don't want to use the observer to populate the recyclerview; in the database I store some objects, but there are more objects that I don't want to store.

So, I try to do this instead (in the ObjectsFragment):

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

And, finally, my problem; I get type mismatch for the true condition assignment:

Type mismatch: inferred type is LiveData<List> but List was expected

I am unable to get my head around this. Isn't this pretty much what is happening in the observer? I know about backing properties, such as explained here, but I don't know how to do that when my data is not defined in the ViewModel.

CodePudding user response:

We need something to switch data source. We pass switching data source event to viewModel.

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

In viewModel we handle switching data source

(To use switchMap include 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
    }
}

In activity/fragment observe getObjects()

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

CodePudding user response:

You can do something like this:

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
}

  • Related