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

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)

Related

How to use navGraphViewModels with custom factory that accepts a parameter? [duplicate]

I have found a similar question here. At the time of writing this question there is only this answer avaliable, which does not provide any help to me, and I believe also to the person who asked the question.
I checked the repo which is linked in the answer and it "solves" the problem by creating an init method in the viewmodel and calling it in the Activity/Fragment.
Since the viewmodel has already been injected, this solution does not seem like ideal to me, and I would like to know if there are other options available when using hilt.
As per this comment and the release of AndroidX Hilt 1.0.0-alpha03, Hilt has supported ViewModels that take a SavedStateHandle as a parameter (right alongside your other injected parameters).
This SavedStateHandle is automatically, without you doing anything, populated with the arguments passed to your fragment (i.e., the same arguments you get from requireArguments() and the same arguments that are read by Safe Args).
Therefore in your ViewModel's constructor, you can immediately access those arguments from the SavedStateHandle, without having to do any manual passing of arguments to your ViewModel.
#HiltViewModel
class MainViewModel #Inject constructor(
val userDataManager: UserDataManager,
savedStateHandle: SavedStateHandle
) : ViewModel() {
init {
// Use the same argName as in your navigation graph
val yourArgument: String = savedStateHandle["argName"]
// Now use that argument to load your data, etc.
}
}
The feature request for Safe Args integration with SavedStateHandle is already fixed and will be part of the upcoming Navigation 2.4.0-alpha01 release. Once that is released, you'd be able to do something like MainFragmentArgs.fromSavedStateHandle(savedStateHandle) to get the same Args class you're currently able to get from by navArgs() within your ViewModel.
For anyone facing the same challenge and using Hilt, the trick is to initialize the SavedStateHandle in the viewModel constructor i.e.
savedStateHandle: SavedStateHandle = SaveStateHandle()
Then you can access the passed argument like:
val id: String? = savedStateHandle["id"]
or
val id = savedStateHandle.get<String?>("id")

Initiating a ViewModel with Input parameters in Jetpack Compose -> Composable function

I have a ViewModel that takes a string as an argument
class ComplimentIdeasViewModel(ideaCategory : String) : ViewModel() {
//some code here
}
What is the best way to initiate this ViewModel inside a composable fun without using a ViewModel factory and Hilt? A simple statement seems to achieve this inside a composable fun
#Composable
fun SampleComposableFun() {
val compIdeasViewModel = remember { ComplimentIdeasViewModel("someCategory") }
}
There is no warning in Android studio when I try to do this, but this seems too easy to be true, I am able to do this without Dependency Injection and with a ViewModelFactory class. Am I missing something here?
I've tried how you have written yours out and I had issues with screen rotation resetting the view model. I suspect you may too.
I was able to fix it by utilizing the the factory parameter on viewModel() for this, which worked well for me. See this answer on a similar question with example on how to use it: jetpack compose pass parameter to viewModel
This will not provide you the correct instance if viewmodel. See if you store some state in the viewmodel, then using the factory to initialise it is necessary to ensure that you get the same and latest copy of the viewmodel currently present. There is no error since the syntactic implementation is correct. I do not know of any way to do this because most of the times, you don't need to. Why don't you initialise it in the top-level container, like the activity? Then pass it down wherever necessary.
Create a CompositionLocal for your ViewModel.
val YourViewModel = compositionLocalOf { YourViewModel(...) }
Then initialise it (You'd likely use the ViewModelProvider.Factory here). And then provide that to your app.
CompositionLocalProvider(
YourViewModel provides yourInitialisedViewModel,
) {
YourApp()
}
Then reference it in the composable.
#Composable
fun SampleComposableFun(
compIdeasViewModel = YourViewModel.current
) {
...
}
Note, the docs say that ViewModels are not a good fit for CompositionLocals because they will make your composable harder to test, make your composables tied to this app and make it harder to use #Preview.
Some get pretty angry about this. However, if you manage to mock out the ViewModel, so you can test the app and use #Preview and your composables are tied to the app and not generic, then I see no problem.
You can mock a ViewModel fairly simply, providing its dependencies are included as parameters (which is good practice anyway).
open class MockedViewModel : MyViewModel(
app = Application(),
someOtherDependeny = MockedDependecy(),
)
The more dependencies your ViewModel has the more mocking you'll need to do. But I've not found it prohibitive and including the ViewModel as a default parameter has massively sped up development.

How to separate data handling from Activity

I have a working Activity (TwalksRouteActivity) that accepts a record id (routeID) from a bundle (passed from a Fragment), pulls the associated record from my repository (routesRepository), and passes an associated value/column (routeName) to my UI. This works fine. However, as I understand best practice (I am learning Android development), the call to my Repository should be in a ViewModel, not an Activity. Is this correct? I have tried but failed to do this myself and would really appreciate some help in how to do this please.
TwalksRouteActivity:
class TwalksRouteActivity() : AppCompatActivity() {
private lateinit var viewModel: RouteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Log.i("CWM","Called ViewModelProvider")
//viewModel = ViewModelProvider(this).get(RouteViewModel::class.java)
var bundle: Bundle? = intent.extras
var routeID = bundle?.getInt("routeID")
lifecycleScope.launch (Dispatchers.Main) {
val database = getDatabase(application)
val routesRepository = RoutesRepository(database)
val selectedRoute = routesRepository.getRoute(routeID)
val routeName = selectedRoute.routeName
Log.d("CWM", routeName.toString())
setContentView(R.layout.route_detail)
val routeName_Text: TextView = findViewById(R.id.routeName_text)
routeName_Text.text = routeName.toString()
val routeID_Text: TextView = findViewById(R.id.routeID)
routeID_Text.text = routeID.toString()
}
}
}
You are correct. Best practices include the idea of a ViewModel that handles communications between bussiness logic (your repository) and the activity or fragment which uses or/and dislpays the data. You should check Android Developers ViewModel's official documentation at: ViewModel Overview. Also the guide to app architecture. Check the following image:
As you can see, it describes the data-driven communication flow, and as you said, the ViewModel will call the repository functions that get the data. The ViewModel will then provide the activity with variables and / or functions that can be observed (such as: LiveData), and fire events that the activity will take to make its state changes / data presentation in the UI (this is call reactive pattern).
You should check these Codelabs (free lessons from Google): Incorporate Lifecycle-Aware Components and Android Room with a View - Kotlin (although it mainly covers Room Library, the codelab makes use of ViewModel and Android's best practices recommended by Google). Also, you could check this article: ViewModels and LiveData: Patterns + AntiPatterns.
I could write a lot of code but I think it is beyond the scope of this answer. I'm also learning, and my way was to first understand how these things work and why these things are called "best practices".

Difference between lazy{} vs getter() initialization in kotlin

In kotlin we can use both of these approach lazy{} and getter()
lazy initializaiton:
internal val connector by lazy {
serviceConnector
}
getter():
internal val connector : ServiceConnector
get() = serviceConnector
When to use which approach and what actually does these two approach under the hood. Which one is best approach?
When you use the lazy delegate, the val is initialized only when you use it the first time. So, in your code, the first time you access connector, the code inside the lambda is run, and the result is assigned to the val.
get(), instead, is used to redefine what happens when you try to access the val.

ViewModel backing properties [kotlin]

Looking to the code of some Google's demo app (like sunflower or Google io 2018 app) and I've noticed that for the viemodels' backing properties they use a separate instance of the same type with a custom getter; like this:
private val _userData: MutableLiveData<User>
val userData: LiveData<User>
get() = _userData
but why do they do that? Isn't better to directly make the _userData accessible?
Could it be because while _userData is a MutableLiveData they don't want the observer to be able to change the value?
userData which is exposed to the Activity or Fragment must be immutable since the view only needs to observe to the LiveData. So, we need to make the actual _userData returns a LiveData.
One way is using the Kotlin coding convention and create two variables, _userData and userData, one is mutable and another one is not:
If a class has two properties which are conceptually the same but one
is part of a public API and another is an implementation detail, use
an underscore as the prefix for the name of the private property.

Categories

Resources