I'm trying to get data from a MutableLiveData; however, it seems like something is wrong with the code, can you guys check for me please?
I can get the object, but I failed to add the object to a mutableList
properties = ArrayList()
propertyViewModel.propertyItemLiveData.observe(
viewLifecycleOwner,
Observer { propertyItems ->
for (property in propertyItems){
var p:Property = Property(property.id,property.address
,property.price,property.phone,property.lat,property.lon)
println(p)// i can display data
properties.add(p)//when i add to properties, the properties still null. Why?
}
}
)
if (properties.isEmpty()){
println("null")
}
The code in the observer will only run when propertyItemLiveData pushes a new value, or if it already has a value when you first observe it. But from the docs:
As soon as an app component is in the STARTED state, it receives the most recent value from the LiveData objects it’s observing. This only occurs if the LiveData object to be observed has been set.
So you won't actually get a value until your Activity or Fragment hits the onStart() callback, meaning your observer code won't run until then. If the code you posted is running earlier than that (say in onCreate), then what you're doing is:
creating an empty list
adding an observer that will add stuff to that list (but it won't run until later)
checking if the list is still empty (it definitely is)
Because of the observer pattern, where your code reacts to new data/events being pushed to it, whatever you need to do with that populated list should be part of the observer code. It should react to the new value and take action - update a list view, alert the user, start an API call, whatever
propertyViewModel.propertyItemLiveData.observe(viewLifecycleOwner) { propertyItems ->
// handle the propertyItems, add them to your list etc
// then do whatever needs to happen with the list, e.g. display it
updateDisplay(propertyList)
}
btw if Property is a data class and you're just copying all its data, you can add to your list like this:
properties.addAll(propertyItems.map { it.copy() })
// or propertyItems.map(Property::copy)
hello first of all in kotlin in general you have to use mutableList and the check of empty or any other instruction should inside the call back like this :
properties = mutableListOf<YourClass>()
propertyViewModel.propertyItemLiveData.observe(
viewLifecycleOwner,
Observer { propertyItems ->
for (property in propertyItems){
var p:Property = Property(property.id,property.address
,property.price,property.phone,property.lat,property.lon)
println(p)// i can display data
properties.add(p)//when i add to properties, the properties
}
if (properties.isEmpty()){
println("null")
}
}
)
Related
I need to use flow.collectLatest {} in my fragment in OnViewCreated, and then in a loop under some condition multiple times (I made a filter, so each time different data should be retrieved).
This is my code for calling collectLatest:
viewLifecycleOwner.lifecycleScope.launch {
myViewModel.myFlow.collectLatest { pagingData ->
myAdapter.submitData(pagingData)
myAdapter.notifyDataSetChanged()
}
}
I use this block of code in both onViewCreated and in the loop.
However, it gets called only once, in OnViewCreated.
In the loop, sometimes it gets called, and then it needs 2-3min to retrieve data, but most of the time nothing changes.
I guess it could be an issue related to needing much more time to retrieve data, or it just shouldn't be used this way.
Some of the possible solutions I tried, but didn't work:
using delay
adding flowOn(Despatchers.IO) in the end of the flow
switching flow call to a different thread
You don't need a loop for this, should only collect it on onViewCreated() once time. You should have two flows(one for your filter and another for your data) and to use switcMap(), and your adapter/view should call viewModel to notify any change that to be done.
Here an example:
//you can use any object type for your filter, in this example i used a sealed class
private val _transactionFilter = MutableLiveData<TransactionFilter>(
TransactionFilter.TransactionsByDate(Date())
)
val transactions: LiveData<Data> = _transactionFilter.switchMap { filter ->
//code to return data
}
Kotlin Flow's have a switchMap() too, i used liveData because flow.switchMap was experimental yet.
Another thing: you don't need call notifiyDataSetChanged() when using ListAdapter
I am trying first handle the response from API by using observe. Later after observing the handled variable I want to save it to database.
The variable tokenFromApi is updated inside tokenResponseFromApi's observer. Is it possible to observe tokenFromApi outside the observer of tokenResponseFromApi? When debugged, the code did not enter inside tokenFromApi observer when the app started.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
var tokenResponseFromApi: LiveData<String>? = MutableLiveData<String>()
var tokenFromApi: LiveData<TokenEntity>? = MutableLiveData<TokenEntity>()
tokenResponseFromApi?.observe(viewLifecycleOwner, Observer {
tokenResponseFromApi ->
if (tokenResponseFromApi != null) {
tokenFromApi = viewModel.convertTokenResponseToEntity(tokenResponseFromApi, dh.asDate)
}
})
tokenFromApi?.observe(viewLifecycleOwner, Observer {
tokenFromApi ->
if (tokenFromApi != null) {
viewModel.saveTokenToDB(repo, tokenFromApi)
}
})
}
Your problem is that you're registering the observer on tokenFromApi during setup, and when you get your API response, you're replacing tokenFromApi without registering an observer on it. So if it ever emits a value, you'll never know about it. The only observer you have registered is the one on the discarded tokenFromApi which is never used by anything
Honestly your setup here isn't how you're supposed to use LiveData. Instead of creating a whole new tokenFromApi for each response, you'd just have a single LiveData that things can observe. When there's a new value (like an API token) you set that on the LiveData, and all the observers see it and react to it. Once that's wired up, it's done and it all works.
The way you're doing it right now, you have a data source that needs to be taken apart, replaced with a new one, and then everything reconnected to it - every time there's a new piece of data, if you see what I mean.
Ideally the Fragment is the UI, so it reacts to events (by observing a data source like a LiveData and pushes UI events to the view model (someone clicked this thing, etc). That API fetching and DB storing really belongs in the VM - and you're already half doing that with those functions in the VM you're calling here, right? The LiveDatas belong in the VM because they're a source of data about what's going on inside the VM, and the rest of the app - they expose info the UI needs to react to. Having the LiveData instances in your fragment and trying to wire them up when something happens is part of your problem
Have a look at the App Architecture guide (that's the UI Layer page but it's worth being familiar with the rest), but this is a basic sketch of how I'd do it:
class SomeViewModel ViewModel() {
// private mutable version, public immutable version
private val _tokenFromApi = MutableLiveData<TokenEntity>()
val tokenFromApi: LiveData<TokenEntity> get() = _tokenFromApi
fun callApi() {
// Do your API call here
// Whatever callback/observer function you're using, do this
// with the result:
result?.let { reponse ->
convertTokenResponseToEntity(response, dh.asDate)
}?.let { token ->
saveTokenToDb(repo, token)
_tokenFromApi.setValue(token)
}
}
private fun convertTokenResponseToEntity(response: String, date: Date): TokenEntity? {
// whatever goes on in here
}
private fun saveTokenToDb(repo: Repository, token: TokenEntity) {
// whatever goes on in here too
}
}
so it's basically all contained within the VM - the UI stuff like fragments doesn't need to know anything about API calls, whether something is being stored, how it's being stored. The VM can update one of its exposed LiveData objects when it needs to emit some new data, update some state, or whatever - stuff that's interesting to things outside the VM, not its internal workings. The Fragment just observes whichever one it's interested in, and updates the UI as required.
(I know the callback situation might be more complex than that, like saving to the DB might involve a Flow or something. But the idea is the same - in its callback/result function, push a value to your LiveData as appropriate so observers can receive it. And there's nothing wrong with using LiveData or Flow objects inside the VM, and wiring those up so a new TokenEntity gets pushed to an observer that calls saveTokenToDb, if that kind of pipeline setup makes sense! But keep that stuff private if the outside world doesn't need to know about those intermediate steps
Say that, I'm building a custom compose layout and populating that list as below
val list = remember { dataList.toMutableStateList()}
MyCustomLayout{
list.forEach { item ->
key(item){
listItemCompose( data = item,
onChange = { index1,index2 -> Collections.swap(list, index1,index2)})
}
}
This code is working fine and the screen gets recomposed whenever onChange lambda function is called, but when it comes to any small change in any item's property, it does not recompose, to elaborate that let's change the above lambda functions to do the following
{index1,index2 -> list[index1].propertyName = true}
Having that lambda changing list item's property won't trigger the screen to recompose. I don't know whether this is a bug in jetpack compose or I'm just following the wrong approach to tackle this issue and I would like to know the right way to do it from Android Developers Team. That's what makes me ask if there is a way to force-recomposing the whole screen.
You can't force a composable function to recompose, this is all handled by the compose framework itself, there are optimizations to determine when something has changed that would invalidate the composable and to trigger a recomposition, of only those elements that are affected by the change.
The problem with your approach is that you are not using immutable classes to represent your state. If your state changes, instead of mutating some deep variable in your state class you should create a new instance of your state class (using Kotin's data class), that way (by virtue of using the equals in the class that gets autogenerated) the composable will be notified of a state change and trigger a recomposition.
Compose works best when you use UDF (Unidirectional Data Flow) and immutable classes to represent the state.
This is no different than, say, using a LiveData<List<Foo>> from the view system and mutating the Foos in the list, the observable for this LiveData would not be notified, you would have to assign a new list to the LiveData object. The same principle applies to compose state.
you can recreate an entire composition using this
val text = remember { mutableStateOf("foo") }
key(text.value) {
YourComposableFun(
onClick = {
text.value = "bar"
}
) {
}
}
call this
currentComposer.composition.recompose()
I'm building an App and i made a contact type page with a couple of input text and a button to send a message. Now, i wanted to make the button enabled only when some criteria are met, which are having the three most important fields filled up with some data (Service, Object and Message).
With MVVM pattern and two way databinding, it works just fine but when I observe the data in the fragment the view is linked to, I have some ugly code which looks like this:
contactPageViewModel.serviceToContact.observe(viewLifecycleOwner, Observer { service ->
contactPageViewModel.objectContact.observe(viewLifecycleOwner, Observer { objectContact ->
contactPageViewModel.message.observe(viewLifecycleOwner, Observer { message ->
contact_send_btn.isEnabled = !service.isNullOrEmpty() && !objectContact.isNullOrEmpty() && !message.isNullOrEmpty()
})
})
})
Basically what it does is that it checks if the three fields are full of data, and if its the case it activates the button, which works well.
But my problem is this: Its a nested observer, two times even. So, is it possible to make it look cleaner without having a nested observer to check if the conditions are met ?
Thanks.
You should never have nested observers. It doesn't only look bad, this is a big leak because you're repeatedly creating duplicate observers every time an outer observer gets triggered.
An alternative that keeps your view model as is is to separate the observers and call a function that updates the button in each of them.
contactPageViewModel.serviceToContact.observe(viewLifecycleOwner, Observer { service ->
updateSendButtonEnabled()
})
contactPageViewModel.objectContact.observe(viewLifecycleOwner, Observer { objectContact ->
updateSendButtonEnabled()
})
contactPageViewModel.message.observe(viewLifecycleOwner, Observer { message ->
updateSendButtonEnabled()
})
fun updateSendButtonEnabled() {
val service = contactPageViewModel.serviceToContact.value
val objectContact = contactPageViewModel.objectContact.value
val message = contactPageViewModel.message.value
contact_send_btn.isEnabled = !service.isNullOrEmpty() && !objectContact.isNullOrEmpty() && !message.isNullOrEmpty()
}
BUT! If you want to be as true as possible to the MVVM pattern, this is all still bad because you're doing logic in your view. 100% true to MVVM is to give your viewmodel a single livedata variable that tells the view whether or not to enable the button. Then the only observer and only thing it is doing should look like this:
contactPageViewModel.sendButtonEnabled.observe(viewLifecycleOwner, Observer { isEnabled ->
contact_send_btn.isEnabled = isEnabled
})
I have a recycler view with fixed number widgets vertically in a specific order. Some of the widgets also contain tabular data hence I've considered using nested recycler view also within it.
Every widget makes http call asynchronously from the ViewModel and binds the data to the epoxyController as I mentioned below.
As requestModelBuild() being called for every widget as they receive the data through the public setters for example priceViewData, packageData and etc from where requestModelBuild() is called. So in this instance every widget bind happens regardless of every time when data is received for any of the widgets.
This seems to be expensive also, there some analytics gets fired as we needed for every bind.
So, here the analytics call for the widget is multiplied.
Please suggest if this can be handled through the epoxy without handling manually.
class ProductDetailsEpoxyController(val view: View?,
private val name: String?,
private val context: Context?) :
AsyncEpoxyController() {
private val args = bundleOf("name" to name)
var priceViewData: IndicativePriceViewData? = emptyPriceViewData()
set(value) {
field = value
requestModelBuild()
}
var packageData: PackageViewData? = emptyPackageWidgetViewData()
set(value) {
field = value
requestModelBuild()
}
...
...
override fun buildModels() {
buildPriceViewData()
buildPackageViewData()
....
}
private fun buildPriceViewData(){
priceViewData?.let {
id("price")
priceViewDataModel(it)
}
}
private fun buildPackageViewData(){
packageViewData?.let {
id("package")
packageViewDataModel(it)
}
}
...
...
}
From Epoxy's Wiki:
Adapter and diffing details
Once models are built, Epoxy sets the new models on the backing adapter and runs a diffing algorithm to compute changes against the previous model list. Any item changes are notified to the RecyclerView so that views can be removed, inserted, moved, or updated as necessary.
So basicallly, this ensures not all models will be updated.
The issue that you're facing is possibly related to:
Using DataBinding
Your classes are not implemented equals and hashCode the way you want.
The problem with using Objects in DataBinding is that, every time the object is updated, all fields that depend on the object are also updated, even if not all changed.
If your classes are normal classes and not data classes or you expect a different behavior when executing priceData1 == priceData2 (for example, only comparing the data's id), you should override this methods so Epoxy detect changes correctly. Also, you can use DoNotHash option for EpoxyAttribute so the class is not added to the model's hashCode function. More info