Observe contents of ArrayDeque - android

I have a MutableLiveData of type ArrayDeque.
I am trying to observe it.
It works when I first assign a value to it (create the ArrayDeque) but what I am trying to do is observe changes to the contents i.e. when new entries are added or when entries are removed.
var moveHistory = MutableLiveData<ArrayDeque<Move>>()
..
moveHistory.value = ArrayDeque<Move>() <<--- this fires
moveHistory.value?.addFirst(MontanaMoveStandard(from, to)) <<- this doesn't fire
this is my observe code:
moveHistory.observe(this, Observer {
moveHistory -> undoButton?.isEnabled = moveHistory.size > 0
})

This what I did in the end.
I made my own class MoveHistory which extends MutableLiveData and perform the actions on that. I added to it the methods I use (addFirst and pollFirst) and then reassign value to itself as suggested by Luksprog
This is the class I ended up with:
class MoveHistory(): Serializable, MutableLiveData<ArrayDeque<Move>>(){
init {
value = ArrayDeque<Move>()
}
fun addFirst(move: Move) {
value?.addFirst(move)
value = value
}
fun pollFirst(): Move {
var move = value?.pollFirst()
value = value
return move!!
}
}
Now all I have to do is register my observer on an instance of that class and use the methods to add and remove.

Related

MutableStateFlow is not updating value

I have problem working with MutableStateFlow, I cannot understand how it is working or I am mistaken somewhere. For example purpose I created simpler classes to get the idea what I am doing.
First I have data class which holds the values and controller which update values in the data class
data class ExampleUiState(
val dataFlag: Boolean = false
)
class ExampleController {
private val _exampleUiState = MutableStateFlow(ExampleUiState())
val exampleUiState = _exampleUiState.asStateFlow()
fun onChangeFlag(flag: Boolean) {
_exampleUiState.update { it.copy(dataFlag = flag) }
}
}
I am using koin, and I created Example controller singleton.
Second I am injection it in my ViewModel where I have two functions there
class ExampleViewModel(
private val exampleController: ExampleController
) : ViewModel() {
val exampleUiState = exampleController.exampleUiState.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
ExampleUiState()
)
//called second
private fun useFlagInViewModelFun() {
//here the value is not updated
exampleUiState.value.dataFlag
}
//called first from UI
fun changeValueFromUi(flag: Boolean) {
//change it from default false to true
exampleController.onChangeFlag(flag)
useFlagInViewModelFun()
}
}
The idea is when I call changeValueFromUi from some compose function, I update the value with my controller function, and after it I call other function where I want to use already updated state of data class, but I don't get the correct value.
Where I am mistaken?
Is there any time needed for onChangeFlag() to react and update the value?
Am I mistaken the way that I am trying to get the value after exampleUiState.value.dataFlag ?

Live Data with ArrayList not observing (Kotlin)?

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

Why is Livedata setValue ignored when called twice?

I have the following ViewModel with MutableLiveData data and another LiveData ones that is derived from data in a way that it updates its value only if the data.number is equal to 1.
class DummyViewModel : ViewModel() {
private val data = MutableLiveData<Dummy>()
val ones = data.mapNotNull { it.takeIf { it.number == 1 } }
init {
data.value = Dummy(1, "Init")
doSomething()
}
fun doSomething() {
data.value = Dummy(2, "Do something")
}
}
data class Dummy(val number: Int, val text: String)
fun <T, Y> LiveData<T>.mapNotNull(mapper: (T) -> Y?): LiveData<Y> {
val mediator = MediatorLiveData<Y>()
mediator.addSource(this) { item ->
val mapped = mapper(item)
if (mapped != null) {
mediator.value = mapped
}
}
return mediator
}
I observe ones in my fragment. However, If I execute doSomething, I don't receive any updates in my fragment. If I don't execute doSomething, the dummy Init is correctly present in ones and I receive an update.
What is happening here? Why is ones empty and how can I overcome this issue?
Maybe I'm missing something, but the behavior seems like expected to me...
Lets' try to reproduce both cases sequentially.
Without doSomething() :
Create Livedata
Add Dummy(1, "Init")
Start listening in the fragment: Because number is 1, it passes your filter and the fragment receives it
With doSomething():
Create Livedata
Add Dummy(1, "Init")
Add Dummy(2, "Do something") (LiveData keeps only the last value, so if nobody observes, the first value is getting lost)
Start listening in the fragment: Because number is 2, the value gets filtered and the fragment receives nothing
A little offtopic: it's always good to write tests for ViewModel cases like this, because you'll be able to isolate the problem and find the real reason quickly.
EDIT: also be aware that your filter is only working on observing, it isn't applied when putting the value into LiveData.

Why Transformation.switchMap(anyLiveData) isn't fire when i change the value of "anyLiveData"

I will hope that when i call to "addPlantToGarden()" passing respect "plantId" parameter then fire the "observers" "Transformations.switchMap(plantName)" but that doesn't happen, what is the error?
private val plantName: MutableLiveData<String> = MutableLiveData()
val plant: LiveData<Plant> = Transformations.switchMap(plantName){plantId ->
plantRepository.getPlant(plantId)
}
val isPlanted: LiveData<Boolean> = Transformations.switchMap(plantName){plantId ->
gardenPlantingRepository.isPlanted(plantId)
}
fun addPlantToGarden(plantId: String) {
plantName.value = plantId
}
These are a few things to consider:
1. Check your Repository
Make sure your plantRepository.getPlant(plantId) returns LiveData. Since methods from Repository are executed in background, I prefer encapsulate the function using this:
liveData {
// some async process (e.g. HTTP Request)
emit(/*your value*/)
}
Reference: https://developer.android.com/topic/libraries/architecture/coroutines#livedata
2. Check your Observer
Are you observing on a correct view lifecycle owner? If your ViewModel is inside a Fragment, make sure to do this:
viewModel.plant.observe(viewLifecycleOwner, Observer{
// action
})
instead of:
viewModel.plant.observe(this, Observer{
// action
})
And make sure to observe first before trying to change your plantName value.
3. Start with a simple case
I have no idea how you changed your plantName value. But try from a simple hardcoded/mock value first, for example:
plantName.value = "1"
then trace it through your Repository, then down to your Observer. Hopefully this will help you.

Android - LiveData doesn't get updated

In my fragment I observe dbQuestionsList field:
viewModel.dbQuestionsList.observe(viewLifecycleOwner, Observer { list ->
Log.i("a", "dbQuestionsList inside fragment = $list ")
})
In my fragment I have few buttons and depending on which one is pressed I call method on viewModel passing the string which was set as tag to the button.
viewModel.onFilterChanged(button.tag as String)
My ViewMode:
lateinit var dbQuestionsList: LiveData<List<DatabaseQuestion>>
init{
onFilterChanged("")
}
private fun onFilterChanged(filter: String) {
dbQuestionsList = mRepository.getDbQuestionsForCategory(filter)
}
Repository method:
fun getDbQuestionsForCategory(categoryName: String): LiveData<List<DatabaseQuestion>> {
return database.dbQuestionsDao().getDbQuestionsByCategory(categoryName)
}
Dao method:
#Query("SELECT * FROM db_questions_database WHERE category = :categoryId")
fun getDbQuestionsByCategory(categoryId: String): LiveData<List<DatabaseQuestion>>
When I press button, viewModel method is called with argument which should be used to update LiveData by searching through room database, but NOTHING gets updated for no reason. Database is not empty so there is no reason to return null and not trigger observer in main Fragment.
But when I do this in my viewModel:
lateinit var dbQuestionsList: LiveData<List<DatabaseQuestion>>
init{
onFilterChanged("travel")
}
where I hardcode parameter, the room will return list and observer in fragment will be triggered, so it works like that but doesn't work when arguments is passed when button is pressed, Please explain because this thing doesn't make sense. I tried with mutable live data, with using .setValue and .postValue but NOTHING works.
The reason you aren't getting updates is because onFilterChanged() is reassigning dbQuestionsList, not updating it. So the variable you observe initially is never actually modified.
I would probably implement this using a Transformation:
val filter = MutableLiveData<String>().apply { value = "" }
val dbQuestionsList = Transformations.switchMap(filter) {
mRepository.getDbQuestionsForCategory(filter)
}
Then in your fragment just set the filter when your button is clicked:
viewModel.filter.value = button.tag as String
Try this:
dbQuestionsList.value = mRepository.getDbQuestionsForCategory(filter)
or
dbQuestionsList.postValue(mRepository.getDbQuestionsForCategory(filter))

Categories

Resources