I have MVVM fragment with this simple requirement.
The fragment have a EntryText field, and I want to populate with a value calculated in the view model,
when the viewmodel is created I need to call a Room request and get a value, then that value should be present to the user, and the user can change that value.
Also I'm soring that value in the State Handle
var parcelaDesde = state.get<String>("parcelaDesde")?:"0"
set(value) {
field = value
state.set("parcelaDesde", value)
}
I resolved with:
*creating a public methos in viewmodel that retrieve info from Room and update local member field, also update a MutableLiveData that is observed in Fragment
fun updateParcelaDesde(default: Int) {
val num = parcelaDesde.toIntOrNull()
num?.let {
if (it == default) {
viewModelScope.launch(Dispatchers.IO) {
parcelaDesde = filialcruzaRepository.getMinNroParcela(filial.value?.id!!)?.toString()?:"0"
Log.d(TAG, "updateParcelaDesde: $parcelaDesde")
isLoadingDesde.postValue(false)
}
}
}
}
on the Fragment, I just observe the liveData and update the UI when te loading is completed.
viewModel.isLoadingDesde.observe(viewLifecycleOwner) {
binding.etParcelaDesde.setText( viewModel.parcelaDesde.trim() )
}
Is this the correct way ?
How to do this if I want to us dataBinding?
Best Regards
Related
I have a bottom Sheet Fragment with MVVM which is observing a Firebase Search which is then added to a MutableLiveData.
The .observe(viewLifecycleOwner){it->} is never accessed when data is set from the firebase search even when data is added to the MutableLiveData
private var QrcodeSearch: MutableLiveData<ArrayList<FBAccountNameModel>> = MutableLiveData<ArrayList<FBAccountNameModel>>()
fun QrCodeScanSearch(QRCode: String) {
val profile = ArrayList<FBAccountNameModel>()
db.collection("UserProfiles").orderBy("UserUUID")
.startAt(QRCode)
.endAt("$QRCode\uf8ff")
.limit(5)
.get()
.addOnSuccessListener { snapshot ->
if (snapshot != null) {
Log.d("QRSearchProfileAll", "${snapshot.documents}")
val document = snapshot.documents
document.forEach {
val groupUser = it.toObject(FBAccountNameModel::class.java)
Log.d("QrUser", groupUser.toString())
if (groupUser != null) {
Log.d(
"QrSearchProfile",
groupUser.UserEmail + " " + groupUser.Username + " " + groupUser.UserUUID
)
profile.add(groupUser)
}
}
QrcodeSearch.value = profile
}
}
}
The query from firebase is being received as the correct data is Logged into logCat
internal var qrcodeSearch:MutableLiveData<ArrayList<FBAccountNameModel>>
get() { return QrcodeSearch}
set(value) {QrcodeSearch = value}
groupViewModel.qrcodeSearch.observe(viewLifecycleOwner){it ->
Log.d("QRCodeSearch Observed Data",it.toString())
}
The Observation of the data is never accessed and im unsure where to go even when the MuttableLiveData has data set from .value = , I have also tried *.postValue()
The observer will not get notified if you add a list, because LiveData keeps track of each change as a version number simple counter stored as an int.
Calling setValue() increments this version and updates any observers with the new data only if the observer's version number is less than the LiveData's version,
the side effect is if the LiveData's underlying data structure has changed (such as adding an element to a Collection), nothing will happen to communicate this to the observers.
My Solution is just simple call .toList()
// here
QrcodeSearch.value = profile.toList()
...
here is the another answer
Notify Observer when item is added to List of LiveData
Typically you would use MutableLiveData to modify the data, but use LiveData to observe that data. So can have live data variable pointing to mutable variable.
private var _liveData: MutableLiveData<ArrayList<FBAccountNameModel>> = MutableLiveData<ArrayList<FBAccountNameModel>>()
val liveData: LiveData<ArrayList<FBAccountNameModel>> = _liveData
I have a Composable, a ViewModel and an object of a User class with a List variable in it. Inside the ViewModel I define a LiveData object to hold the User object and in the Composable I want to observe changes to the List inside the User object but it doesn't seem to work very well.
I understand when you change the contents of a List its reference is the same so the List object doesn't change itself, but I've tried copying the list, and it doesn't work; copying the whole User object doesn't work either; and the only way it seems to work is if I create a copy of both. This seems too far-fetched and too costly for larger lists and objects. Is there any simpler way to do this?
The code I have is something like this:
Composable
#Composable
fun Greeting(viewModel: ViewModel) {
val user = viewModel.user.observeAsState()
Column {
// TextField and Button that calls viewModel.addPet(petName)
LazyColumn {
items(user.value!!.pets) { pet ->
Text(text = pet)
}
}
}
}
ViewModel
class ViewModel {
val user: MutableLiveData<User> = MutableLiveData(User())
fun addPet(petName: String){
val sameList = user.value!!.pets
val newList = user.value!!.pets.toMutableList()
newList.add(petName)
sameList.add(petName) // This doesn't work
user.value = user.value!!.copy() // This doesn't work
user.value!!.pets = newList // This doesn't work
user.value = user.value!!.copy(pets = newList) // This works BUT...
}
}
User
data class User(
// Other variables
val pets: MutableList<String> = mutableListOf()
)
MutableLiveData will only notify view when it value changes, e.g. when you place other value which is different from an old one. That's why user.value = user.value!!.copy(pets = newList) works.
MutableLiveData cannot know when one of the fields was changed, when they're simple basic types/classes.
But you can make pets a mutable state, in this case live data will be able to notify about changes. Define it like val pets = mutableStateListOf<String>().
I personally not a big fan of live data, and code with value!! looks not what I'd like to see in my project. So I'll tell you about compose way of doing it, in case your project will allow you to use it. You need to define both pets as a mutable state list of strings, and user as a mutable state of user.
I suggest you read about compose states in the documentation carefully.
Also note that in my code I'm defining user with delegation, and pets without delegation. You can use delegation only in view model, and inside state holders you cannot, othervise it'll become plain objects at the end.
#Composable
fun TestView() {
val viewModel = viewModel<TestViewModel>()
Column {
// TextField and Button that calls viewModel.addPet(petName)
var i by remember { mutableStateOf(0) }
Button(onClick = { viewModel.addPet("pet ${i++}") }) {
Text("add new pet")
}
LazyColumn {
items(viewModel.user.pets) { pet ->
Text(text = pet)
}
}
}
}
class User {
val pets = mutableStateListOf<String>()
}
class TestViewModel: ViewModel() {
val user by mutableStateOf(User())
fun addPet(petName: String) {
user.pets.add(petName)
}
}
Jetpack Compose works best with immutable objects, making a copy with modern Android and ART is not the issue that it was in the past.
However, if you do not want to make a whole copy of your object, you could add a dummy int to it and then mutate that int when you also mutate the list, but I strongly urge you to consider immutability and instantiate a new User object instead.
I am facing and issue with Android LiveData and Transformation map. I am gonna explain the case:
I have a SingleLiveEvent and LiveData as follows (one for all items and another one for items to display in screen):
val documents: SingleLiveEvent<List<DocumentData>> = SingleLiveEvent()
val itemsToDisplay: LiveData<List<DocumentData>>
get() {
return Transformations.map(documents) { documents ->
return#map documents.filter { showOptionals || it.isMandatory }
}
}
In Fragment, after observing itemsToDisplay, if I am trying to get the value of itemsToDisplay LiveData (itemsToDisplay.value) is always null
itemsToDisplay.observe(this, Observer {
// Inside this method I need to get a calculated property from VM which uses
```itemsToDisplay.value``` and I would like to reuse across the app
loadData(it)
})
// View Model
val hasDocWithSign: Boolean
get() {
return **itemsToDisplay.value**?.any { it.isSignable } ?: false
}
Does anyone know if a LiveData does not hold the value if it is calculated using Transformation.map or it could be a potential bug?
When you call itemsToDisplay you get a new empty LiveData instance, because you declared it as a getter without a backing field.
val itemsToDisplay: LiveData<List<DocumentData>>
= Transformations.map(documents) { documents ->
documents.filter { showOptionals || it.isMandatory }
}
https://kotlinlang.org/docs/reference/properties.html#backing-fields
I am using one of Android Jetpack Component ViewModel + Live data in my project it works fine for me when using normal data such as string and Int but when it comes to arrayList it won't observe anything
Here's my code
class MainActivityModel : ViewModel() {
private var dataObservable = MutableLiveData<ArrayList<Int>>()
init {
dataObservable.value = arrayListOf(1,2,3,4,5)
}
fun getInt(): LiveData<ArrayList<Int>> = dataObservable
fun addInt(i:Int) {
dataObservable.value!![i] = dataObservable.value!![i].plus(1)
}
}
A LiveData won't broadcast updates to observers unless its value is completely reassigned with a new value. This does not reassign the value:
dataObservable.value!![i] = dataObservable.value!![i].plus(1)
What it does is retain the existing array, but add an array element. LiveData doesn't notify its observables about that. The actual array object has to be reassigned.
If you want to reassign a new array value and notify all observers, reassign a new array altogether, like this:
dataObservable.value = dataObservable.value!![i].plus(1)
Assigning dataObservable.value will call the LiveData setValue() method, and notify observers of the new value passed to setValue() .
If you modify your complex observable object outside of main thread you need to use postValue
dataObservable?.value[i] += 1
dataObservable.apply {
postValue(value)
}
The docs show how you can perform Transformations on a LiveData object? How can I perform a transformation like map() and switchMap() on a MutableLiveData object instead?
MutableLiveData is just a subclass of LiveData. Any API that accepts a LiveData will also accept a MutableLiveData, and it will still behave the way you expect.
Exactly the same way:
fun viewModelFun() = Transformations.map(mutableLiveData) {
//do somethinf with it
}
Perhaps your problem is you dont know how does yor mutable live data fit on this.
In the recent update mutable live data can start with a default value
private val form = MutableLiveData(Form.emptyForm())
That should trigger the transformation as soon as an observer is attached, because it will have a value to dispatch.
Of maybe you need to trigger it once the observer is attached
fun viewModelFun(selection: String) = liveData {
mutableLiveData.value = selection.toUpperCase
val source = Transformations.map(mutableLiveData) {
//do somethinf with it
}
emitSource(source)
}
And if you want the switch map is usually like this:
private val name = MutableLiveData<String>()
fun observeNames() = Transformations.switchMap(name) {
dbLiveData.search(name) //a list with the names
}
fun queryName(likeName: String) {
name.value = likeName
}
And in the view you would set a listener to the edit text of the search
searchEt.doAfterTextChange {...
viewModel.queryName(text)
}