Android databinding and LiveData: Can't bind to value in LiveData property - android

I'm trying out databinding for a view that's supposed to display data exposed through a LiveData property in a viewmodel, but I've found no way to bind the object inside the LiveData to the view. From the XML I only have access to the value property of the LiveData instance, but not the object inside it. Am I missing something or isn't that possible?
My ViewModel:
class TaskViewModel #Inject
internal constructor(private val taskInteractor: taskInteractor)
: ViewModel(), TaskContract.ViewModel {
override val selected = MutableLiveData<Task>()
val task: LiveData<Task> = Transformations.switchMap(
selected
) { item ->
taskInteractor
.getTaskLiveData(item.task.UID)
}
... left out for breivety ...
}
I'm trying to bind the values of the task object inside my view, but when trying to set the values of my task inside my view I can only do android:text="#={viewmodel.task.value}". I have no access to the fields of my task object. What's the trick to extract the values of your object inside a LiveData object?
My task class:
#Entity(tableName = "tasks")
data class Task(val id: String,
val title: String,
val description: String?,
created: Date,
updated: Date,
assigned: String?)

For LiveData to work with Android Data Binding, you have to set the LifecycleOwner for the binding
binding.setLifecycleOwner(this)
and use the LiveData as if it was an ObservableField
android:text="#{viewmodel.task}"
For this to work, Task needs to implement CharSequence. Using viewmodel.task.toString() might work as well. To implement a two-way-binding, you'd have to use MutableLiveData instead.

why are you using two way binding for TextView
android:text="#={viewmodel.task.value}"
instead use like this android:text="#{viewmodel.task.title}"

Related

How does ViewModel cache LiveData?

I saw all of the following scenarios in different example projects from Google's Codelabs and other sources and do not fully understand where the values from the LiveData object are retrieved from.
Scenario 1 - Current Understanding:
According to https://developer.android.com/.../viewmodel one reason to use a ViewModel is to store/cache UI related data that I want to re-use after the corresponding UI has been rebuild after a configuration change.
Given the following simplified ViewModel and Repository: After updateName() is called the first time, the LiveData object of _currentName contains a String. If the UI is then rebuild after a screen rotation, the view that needs to display the current name requests it by observing currentName which in turn returns the value of the LiveData object that is contained in the field of the _currentName property. Am I correct?
ViewModel
class NamesViewModel(): ViewModel() {
private val respository = NamesRepository()
private val _currentName: MutableLivedata<String?> = MutableLiveData(null)
val currentName: LiveData<String?> get() = this._currentName
...
// Called as UI event listener.
fun updateName() {
this._currentName.value = this.repository.updateName()
}
}
Repository
class NamesRepository() {
fun updateName(): String {
val nextName: String
...
return nextName
}
}
Scenario 2:
What happens if the UI is rebuild after a screen rotation in the following case? _currentName in the ViewModel 'observes' currentName in the repository, but it still is a property and therefore stores its own LiveData object in its field. When the view then requests currentName from the ViewModel, the value is retrieved from the LiveData object that is contained in the field of the _currentName property in the ViewModel. Is this correct?
ViewModel
class NamesViewModel(): ViewModel() {
private val respository = NamesRepository()
private val _currentName: LiveData<String?> = this.repository.currentName
val currentName: LiveData<String?> get() = this._currentName
...
// Called as UI event listener.
fun updateName() {
this.repository.updateName()
}
}
Repository
class NamesRepository() {
private val _currentName: MutableLivedata<String?> = MutableLiveData(null)
val currentName: LiveData<String?> get() = this._currentName
fun updateName() {
val nextName: String
...
this._currentName.value = nextName
}
}
Scenario 3:
In the following scenario, if the UI is rebuild and a view requests currentNam from the ViewModel, where is the requested value stored? My current understanding is, that currentName falls back to the field of the property _currentName in the repository. Isn't that against the idea of the ViewModel to store relevant UI data to be re-used after a configuration change? In the case below, it might be no problem to retrieve the value from the repository instead of the viewModel, but what if the repository itself retrieves the value directly from a LiveData object that comes from a Room database? Wouldn't a database access take place every time a view requests _currentName from the viewModel?
I hope somebody can clarify the situation more, in order to understand how to cache UI related data in the viewModel the correct way (or at least to understand what are the incorrect ways).
ViewModel
class NamesViewModel(): ViewModel() {
private val respository = NamesRepository()
val currentName: LiveData<String?> get() = this.repository.currentName
...
// Called as UI event listener.
fun updateName() {
this.repository.updateName()
}
}
Repository
class NamesRepository() {
private val _currentName: MutableLivedata<String?> = MutableLiveData(null)
val currentName: LiveData<String?> get() = this._currentName
fun updateName() {
val nextName: String
...
this._currentName.value = nextName
}
}
To answer your question scenario#1 is correct usage of LiveData.
Firstly, LiveData is not responsible for caching, it is just LifeCycleAware Observable, given that caching is done at ViewModel, when your activity recreates due to any configuration changes, android will try to retrieve the existing instance of ViewModel, if found then it's state and data are retained as is else it will create a new instance of ViewModel.
Second, using LiveData in repository is a bad idea at many levels, repository instances are held by ViewModel and LiveData are part of Android Framework which makes repositories rely on Android Framework thus creating problems in Unit Testing. Always use LiveData only in ViewModels.

viewmodel two way data binding with list type data Kotlin in android

have this code, in viewModel (kotlin)
private val _shoeData = MutableLiveData<List<Shoe>>()
val shoeData : LiveData<List<Shoe>>
get() {
return _shoeData
}
how two-data-binding can be implemented in a View (e.g: Fragment), like below wouldn't work
android:text="#={shoeStoreViewModel.shoeData.name}"
as shoeData is now a list

Android LiveData Transformation: Changing LiveData object value

I have one LiveData object that holds a list of Users and I am trying to transfer over the data to another LiveData object to be used elsewhere.
I am using MVVM with Room so I get LiveData from the database and on the ViewModel, I am trying to convert the User object in the LiveData to a Person object to show in the UI.
So I have one variable that is LiveData<List<User>>
class User(var firstName: String, var lastName: String, var age: Integer)
and I am trying to convert it to LiveData<List<Person>> (as an example)
class Person() {
lateinit var firstName: String
lateinit var age: Integer
}
and the way I am trying to change them is by using LiveData Transformations.map
ViewModel:
val list2: LiveData<List<User>> = repo.getAll()
var liveList: LiveData<ArrayList<Person>> = MutableLiveData()
liveList = Transformations.map(list2) { list ->
val newList: ArrayList<Person> = ArrayList()
list?.forEach {
val temp = Person()
temp.firstName = it.firstName
temp.age = it.age
newList.add(temp)
}
return#map newList
}
but when I run it, it crashes or doesn't update the UI.
Thanks!
The main problem with your code is that it uses var in var liveList: LiveData instead of using val.
You should declare the liveList variable like this:
val liveList = Transformations.map(list2) { list ->
...
}
Why?
Generally, a LiveData variable should always be declared with val. The reason is that the purpose of LiveData is to allow us to observe the up-to-date value held by the LiveData. We do it by code like this:
liveList.observe(this) { list ->
showList(list)
}
With this code, we ensure that the updated list is always shown. Whenever the list value which is held by liveList changes, the UI is updated as a result. But if the liveList itself also changes, the code will only observe the first LiveData of the liveList variable, and the UI will not be updated correctly.
val liveList = MutableLiveData(repo.getAll().value.orEmpty().map { user ->
Person(user.firstName, user.age)
})
This would be a more compact way, you could pull out the repo.getAll() call into its own variable if you like

Use of generics in databinding and live data

I use LiveData and databinding in my app to populate a recyclerview from the viewmodel.
The property holding the items is defined as
abstract val searchItems : LiveData<List<BindableItem<*>>>
However, databinding is stripping the type for the LiveData and generating an Object type live data, which fails to compile.
This is the generated code for the property above:
android.arch.lifecycle.LiveData searchViewModelSearchItems = null;
For other non-generics properties the type is retained, for instance
android.arch.lifecycle.LiveData<java.util.List<com.example.feature.search.adapter.CarouselItem>> searchViewModelCarouselItems = null;
How can use generics with databinding?
I have not found a way to get the databinding compiler to accept a list of generics, but a workaround is to define the list to be of type Any and then cast to List<BindableItem<*>> in the binding adapter that adds the items to the recyclerview. Not ideal, but at least works.
In the viewmodel:
abstract val items: LiveData<List<Any>>
In the binding adapter:
fun setItems(
recyclerView: RecyclerView,
items: List<Any>?,
onItemClickListener: OnItemClickListener?
) {
val bindableItems = items as? List<BindableItem<*>>
/* do other stuff */
}

Livedata class inner members in databinding

if I have a class
data class item(val address: String = ""
)
its declared in my viewmodel
var varLive: MutableLiveData = MutableLiveData()
and later on I post it from my viewmodel
varLive.postValue(scootersList[marker])
in my xml I have
<TextView
...
android:text="#{vModel.varLive.address}"
/>
And I can't access item.address and get a databinding error.
I can check if the varLive is null and tht is it
Do I really have to declare each of the livedata class fields as a live data? If I have a class holding 100 members?
for some stupid reason you have to specify a getter method in your viewmodel, so databinding can pick it up. like so:
fun getvarLive() = varLive
Kotlin actially does that for you. But databinding won't bind Kotlin getters. Seriosly annoying

Categories

Resources