Pass value of a MutableLiveData of ViewModels - android

I'm very very new to Android and I'm trying to understand/integrate Data Binding.
I think I'm missing something since it always displays null
Here is my ViewModel
class LockDetailsModel: ViewModel() {
var name = MutableLiveData<String>()
fun setName(name: String) {
this.name.value = name
}
In my LockActivity, I set the value of name based on a certain field.
val viewModel: LockDetailsModel by viewModels()
viewModel.setName(unitName.toString())
And in my other activity, which displays the layout, this is what I did to bind the model
val viewModel: LockDetailsModel by viewModels()
binding: ActivityConnectLockBinding = DataBindingUtil.setContentView(this, R.layout.activity_connect_lock)
binding.lockDetails = viewModel
binding.lifecycleOwner = this
It displays null :(
Thanks in advance for your help!

The reason behind you getting null value is because you are using different viewModel instances in both the activities , thus the value stored by you in first Activity cannot be obtained in the Second activity . You need to have SharedViewModel between both the activities , which is not possible due to Single Activity principle . So to implement logic that you want to implement you need to have Fragments and make use of fragment-ktx library to easily create SharedViewModel .If you want an example you can refer to my answer here :
Want to show progress bar while moving to the previous page from the current page
The above answer describes how to get progress of a download started in one activity into another .

Related

Shared ViewModels in modules

I have an Activity with multiple fragments under it. I want to share data with the Shared ViewModel, but they are all in different Modules. I can't get the ActivityViewModel Class. When I received activityViewModels() using CoreViewModel, the parent of ActivityViewModel, I got an object that was different from what I received with ActivityViewModel,How can I Share ViewModel in multiple Modules
This is the log I printed after I moved them to the same Module,But that's not the way I wanted it done
In MainActivity
extend ->MainViewModel:CoreViewModel:ViewModel
val mV0 by viewModels<MainViewModel>()
LogD("mViewModel0 = $mV0")
In MineFragment(The Fragment is MainActivity child,To get MainViewModel.class I moved MineFragment to the same module as MainActivity)
val mV1 by activityViewModels<MainViewModel>()
val mV2 by activityViewModels<CoreViewModel>()
LogD("mViewModel1 = $mV1")
LogD("mViewModel2 = $mV2")
Log info
mViewModel0 = com.nf.bitcoinexchange.activities.main.MainViewModel#6505362
mViewModel1 = by activityViewModels<MainViewModel>()= com.nf.bitcoinexchange.activities.main.MainViewModel#6505362
mViewModel2 = by activityViewModels<CoreViewModel>()= com.nf.corelib.viewmodel.CoreViewModel#3290057
The translation software I use may be a little unclear... sorry

Android different ways to create viewModel object which one to use when?

I recently started with the ViewModel and AndroidViewModel, I see there are different approach to initialise a viewModel instance, for me all works fine, I just want to know which one to use when? and where should I initialise the viewModel object? following all are the different approach to get the viewModel instance and works for me:
val myViewModel1 = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MyViewModel::class.java)
val myViewModel2 = ViewModelProvider.AndroidViewModelFactory(this.application).create(MyViewModel::class.java)
val myViewModel3 = ViewModelProvider(this).get(MyViewModel::class.java)
val myViewModel4: MyViewModel by viewModels()
val myViewModel5 by viewModels<MyViewModel>()
The easiest and most simple for me are 3rd, 4th and 5th, however I don't know what is the difference in all the five approaches, also please let me know if there any other way or optimal way to initialise my viewModel object, I do the initialisation on the global variable while declaring it, is it okay to initialise at the declaration time or it should be done inside some lifecycle method?
In case anyone looking for in depth answer, please check this, here we have the following way to create or get the viewModel object:
val myViewModel1 = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MyViewModel::class.java)
myViewModel2 = ViewModelProvider.AndroidViewModelFactory(this.application).create(MyViewModel::class.java)
val myViewModel3 = ViewModelProvider(this).get(MyViewModel::class.java)
val myViewModel4: MyViewModel by viewModels()
val myViewModel5 by viewModels<MyViewModel>()
All do the same thing, the only two key differences is:
The viewModel initialisation with lazy loading and without lazy loading.
The viewModel with multiple parameter and no parameters.
Lets see this wrt the lazy loading and without lazy loading, the first three are without the delegate by that means there is no lazy loading of that object, so it's the developer
responsibility to create the viewModel object only when activity is created or the fragment is attached to the activity, that means the first three approach(1, 2, 3) can't be
used at global scope, if used at global scope the variable must be
a var with lateint or null initialisation, and the
initialisation(approach 1, 2, 3) must happen in the onCreate or
onViewCreated(in case of fragment).
Therefor the best way to create the viewModel object is using the delegate by(4, 5), both are same with a bit different syntax, I choose 4 because of it's simplicity and readability.
val myViewModel4: MyViewModel by viewModels()
The by delegate gives the flexibility to lazy load the instance and you can define the viewModel at global scope and get ride off the boilerplate code, if you try to initialise the viewModel at global scope without the delegate the app will crash since the viewModel will try to initialise before the activity is created(it will not lazy load the viewModel instance).
Now let's see how to lazy load with multiple parameters, the 6th approach not mention in the question.
If you have multiple parameters in your view model and not using any dependency injection, you can use a ViewModelFactory implementation and then lazy load it:
val myViewModelWithParm: MyViewModel by viewModels { MyViewModelFactory(application, "param1", "param2") }
ViewModelFactory implementation:
class MyViewModelFactory(val application: Application, val param1: String, val param2: String) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MyViewModel(application, param1, param2) as T
}
}
Till this point we are clear on the delegate initialisation(4, 5), and how it is different with(1, 2, 3) now let's see the difference on the top 3 approach(1, 2, 3).
Let's first check 1 and 2.
val myViewModel1 = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MyViewModel::class.java)
myViewModel2 = ViewModelProvider.AndroidViewModelFactory(this.application).create(MyViewModel::class.java)
The key difference in them is one uses ViewModelProvider.NewInstanceFactory and other uses ViewModelProvider.AndroidViewModelFactory, so I checked the source code of both the classes and found that ViewModelProvider.AndroidViewModelFactory is actually the implementation of ViewModelProvider.NewInstanceFactory which override the create function that means both are doing the same stuff, preferable both approach should be chosen if we want multiple parameters however for that we have to override ViewModelProvider.NewInstanceFactory to create our own factory like it's done here
Now comes the third one:
val myViewModel3 = ViewModelProvider(this).get(MyViewModel::class.java)
This is the simple form of 1 and 2 when we don't have multiple parameters in our ViewModel and don't want to lazy load the object.
Note: I highly recommend the approach 4 or 5(both are same with different syntax), since this is the most suitable and optimal to write, if you don't have multiple arguments, in case you have multiple arguments you can use the approach 6 mentioned in the answer by implementing ViewModelProvider.Factory.
3 is the standard way to fetch (and create if necessary) a ViewModel with no constructor parameters. Internally, that's doing 1 to pass a factory that calls the empty constructor (NewInstanceFactory()).
An AndroidViewModel is a subclass of ViewModel that automatically passes in an application reference, in case you need access to things like the application context. So even if your AndroidViewModel has no parameters, the factory that creates it needs to pass in an application, which is what 2 is doing.
This is all taken care of for you by default using 3 - you only need to define and use a factory if your VM needs to be configured with some extra parameters.
4 and 5 are the same thing, just with the type specified in a different place (you only need one declaration and the other will be inferred). They're delegates from the KTX libraries, and they do the same thing as 3, but they're much more readable IMO - especially if you're mixing scopes, like using by viewModels to get a Fragment's own VM, and also by activityViewModels to get the Activity's VM to share data with that and other Fragments.
They're also lazy delegates (as far as I'm aware!) meaning the VM only gets instantiated when it's first accessed, which is generally going to happen later in the lifecycle (instead of when the object is first constructed). I'm not sure if there is a problem initialising the VM on construction, but all of the official examples I've seen seem to fetch it in onCreate (or thereabouts)

retreiving data from Room as LiveData does not trigger observer in some cases only

In my app i use the Room library to handle user data, all the functionality has been implemented like in the "Android Basics in Kotlin" Tutorial Unit 5 on developer.android.com.
In one fragment i need to fetch a single item out of the database - for that i implemented the function in the fragment's viewmodel:
fun retrievePlaceItem(id: Int): LiveData<PlaceItem> {
return itemDao.getPlaceItem(id).asLiveData()
}
the ItemDao is passed into the ViewModel Factory from the Room Database instance, which itself is instantiated in the custom Application class.
this is the query used in the ItemDao interface:
#Query("SELECT * FROM placeItem WHERE id = :id")
fun getPlaceItem(id: Int): Flow<PlaceItem>
Data in the ItemDao is returned as a Flow, and turned into LiveData in the fetching function.
The Fragment itself observes the return of the function with a passed id, and when the observer triggers, the value is stored in a lateinit var of the corresponding datatype.
lateinit var placeItem: PlaceItem
...
override fun onViewCreated(...) {
super.onViewCreated(view, savedInstanceState)
...
val id = navigationArgs.itemId
sharedViewModel.retrievePlaceItem(id)
.observe(this.viewLifecycleOwner) { selectedItem ->
placeItem = selectedItem
}
...
}
this works flawlessly, the item is retrieved, the observer gets triggered, and the lateinit var placeItem is initialized for further use.
in another fragment, that follows later on, i use a different viewmodel with the exact same function - i try to retrieve the value in the exact same way, observing the function return within the onViewCreated method of the fragment. the code is exactly the same, and i tried comparing it to the things taught in the tutorial - no deviations whatsoever. when i now go to use the value, i get an error
kotlin.UninitializedPropertyAccessException: lateinit property placeItem has not been initialized
after inspecting my code using logs, i understood the following:
the viewmodel function to retrieve the item is called
the correct item id is used
the code inside the observer curly brackets is not executed
i tried using the same viewmodel in both fragments, anything until there was no more conceivable difference between these two pieces of code. yet the first one works, the second one doesn't. something in my code creates a difference between the two instances of me using the database to fetch an item.
I avoided the problem by moving the other functions, that will handle the lateinit var, into the observer brackets. i'm unsure as to why that was not needed in my other fragment, but this works just fine!

How to transfer data from one Fragment to another?

I am writing an application on Kotlin (Android Studio), using jetpack.navigation architecture.
There are two fragments: The first contains a list with class instances, which I display in the RecyclerView, the second for EditText (I fill in the client data). I also use Livedata and ViewModel.
The problem is that when I go to the second fragment, fill in the data and confirm, I go to the 1st fragment. As I understand it, the following lines destroy the old Fragment1, and create a new one. the list on the first fragment is reset to zero (although the list is saved when you rotate the screen and minimize the application).
val client = Clients(id,name,secondName,thirdName, address, creditCard, bankNum)
val action = Fragment2Directions.actionFragment2ToFragment1(client)
findNavController().navigate(action)
I could not find how to solve problem using the navigation component. I will be very grateful.
To pass data between two fragments with jetpack navigation you have to use Safe Args
pass an argument section like
<fragment android:id="#+id/myFragment" >
<argument
android:name="myArg"
app:argType="integer"
android:defaultValue="0" />
</fragment>
add classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" in top level gradle file
and add the plugin apply plugin: "androidx.navigation.safeargs.kotlin"
now send the value like so
override fun onClick(v: View) {
val amountTv: EditText = view!!.findViewById(R.id.editTextAmount)
val amount = amountTv.text.toString().toInt()
val action = SpecifyAmountFragmentDirections.confirmationAction(amount)
v.findNavController().navigate(action)
}
and receive it as
val args: ConfirmationFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val tv: TextView = view.findViewById(R.id.textViewAmount)
val amount = args.amount
tv.text = amount.toString()
}
However safeargs works only for primitive types so you have to deconstruct and reconstruct if you're trying to pass Objects
As you are using ViewModels, I recommend that you use a shared ViewModel. The way it works is that multiple Fragments within the same Activity have access to the same ViewModel instance.
There is an example on Android Developers that fits your use case exactly. It shows how to use a shared ViewModel to do this using the master-detail navigation pattern and LiveData. I recommend you take a look at it.
Why not to use Safe Args here: You can try using Safe Args to achieve what you are trying to achieve, but I strongly recommend against it: You would have to deal with somehow using Safe Args to pass your Client objects between the Fragments back and forth (which means either sending each field individually or bundling) and you would have to manually update your LiveData objects - which defeats the purpose of LiveData. Using a shared ViewModel, you do not have to worry about any of that. No sending data back and forth, no taking your Client objects apart or bundling, no manual updating of LiveData objects - you simply access the same LiveData instance from both Fragments through your ViewModel.

How to use livedata with Transformations.switchMap correctly to get initial data?

right now I am starting to use LiveData for the first time. First I put all of my code in the viewModel including the code to start a search in the server.
I used LiveData like this:
Fragment onViewCreated()
viewModel.changeNotifierContacts.observe(this, androidx.lifecycle.Observer { value -> value?.let {
recyclerViewAdapter.setData(value)
} })
This was working as expected. Now I add a repository layer following MVVM pattern. (For this I moved my contact search functionality to repository class)
First I implemented the connection between ViewModel and repository like this:
ViewModel code:
fun getContacts(): MutableLiveData<ContactGroup> {
return contactSearchRepository.changeNotifierContacts;
}
fun search(newSearchInput: String) {
contactSearchRepository.searchInRepository(newSearchInput)
}
Now I read this article that told us to not use LiveData like this: https://developer.android.com/topic/libraries/architecture/livedata#merge_livedata
Example from this page:
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
private fun getPostalCode(address: String): LiveData<String> {
// DON'T DO THIS
return repository.getPostCode(address)
}
}
Instead we should use something like this:
var changeNotifierContacts : LiveData<ContactGroup> = Transformations.switchMap(searchInput) {
address -> contactSearchRepository.getPostCode(address) }
Questions:
Did I understand this article correctly or can I use my first implementation?
In my constructor of the viewModel I am creating an instance of my repository object that is starting to observe server data and it is getting initial data. (For example I am getting a list of all my friends). I am getting this initial data if I am using my first implementation. If I am using Transformations.switchMap implementation I am not getting this initial data. I first have to start a search here to get updated data then. This is not what I want, I also need to display "my friends" list without doing a search.
Is there another approach I can use here? Maybe LiveData is not the best solution to connect ViewModel with Repository?
Thanks for responses and suggestions!
Did I understand this article correctly or can I use my first implementation?
I think you did, but I believe you have expanded the concept too much.
If you are expecting the user to enter a search to receive an answer, you should do like they said:
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
private val addressInput = MutableLiveData<String>()
val postalCode: LiveData<String> = Transformations.switchMap(addressInput) {
address -> repository.getPostCode(address) }
fun setInput(address: String) {
addressInput.value = address
}
}
However, if you are loading a default list, you should do it like you did in your first example:
val getContact = contactSearchRepository.changeNotifierContacts
In this case you will have to observe getContact and postalCode.
In my constructor of the viewModel I am creating an instance of my repository object that is starting to observe server data and it is getting initial data. (For example I am getting a list of all my friends). I am getting this initial data if I am using my first implementation. If I am using Transformations.switchMap implementation I am not getting this initial data. I first have to start a search here to get updated data then. This is not what I want, I also need to display "my friends" list without doing a search.
You can start your fragment/activity with a default search, like this:
MyViewModel.setInput("Friends")
This way you do not need to observe two objects as postalCode will provide all answers.
Is there another approach I can use here? Maybe LiveData is not the best solution to connect ViewModel with Repository?
I think live data is your answer. After done with the learning curve it becomes easier to deal with!
I hope it helps!

Categories

Resources