How to get ViewModel in Activity in Android SunFlower Project? - android

I am studying MVVM of Google Android SunFlower project.
For Fragment, it gets viewmodel like the following
private val plantDetailViewModel: PlantDetailViewModel by viewModels {
InjectorUtils.providePlantDetailViewModelFactory(requireActivity(), args.plantId)
}
I want to try the same method to get viewmodel in Activity. but the requireActivity()show unresolved reference...
And the data binding is not working when I replace it to this.
Does it has other pattern can be use for providePlantDetailViewModelFactory()
Thanks in advance.

You have to add these to your build.gradle
implementation "androidx.core:core-ktx:1.3.1"
implementation "androidx.fragment:fragment-ktx:1.2.5"
Usage in Activity
val usersViewModel: UsersViewModel by viewModels()
Usage in Fragment
// Get a reference to the ViewModel scoped to this Fragment
val viewModel by viewModels<MyViewModel>()
// Get a reference to the ViewModel scoped to its Activity
val viewModel by activityViewModels<MyViewModel>()

You need a gradle dependency on "androidx.activity:activity-ktx:$version".
Then you can import androidx.activity.viewModels in your Activity.

I don't think so that you can do similar for your Activity that's done for Fragment.
Why?
The delegate function viewModels is available from fragment-ktx library (Refer here for reference) as extension for getting ViewModel instance lazily on your value objects using by keyword.
And there's no such thing available for Activities, for an instance.
So, better is to create your own or find some other way like basic lazy{} function from Kotlin standard library and providing logic to it.

Related

What is the better MVVM approach to define a ViewModel class?

I've seen many tutorials for MVVM. Most of them say that you need to define your ViewModel class like this:
class MainViewModel: ViewModel() {
...
}
But recently I stumbled upon Dagger tutorial project from Google. There is a different ViewModel class definition:
class MainViewModel(private val userDataRepository: UserDataRepository) {
...
}
So I wonder, what is the difference between these two approaches?
That's not a relevant comparison. That CodeLab uses a non-ViewModel ViewModel class to simplify their explanation of how DI works. Notice it doesn't subclass ViewModel. Also, the project starts without the dependency injection and has you add it in later, so the starting project isn't intended to be a good example of how to design something.
Either way, if you have a repository, you need some way to get a reference to the repository in your ViewModel. If it is through the constructor, you would have to get a reference to the repository in the associated ViewModelFactory that you build for this class. If you use Dagger, you'll probably let Dagger generate this factory for you and inject the reference.
If your ViewModel doesn't use a repository, then you won't have any reason to have one in your constructor, with or without dependency injection. Many basic MVVM tutorials are going to start with the most basic possible example, a ViewModel with no arguments needed. That doesn't imply that a ViewModel should never have dependencies.

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 mock the view model with Hilt for unit testing fragments?

I've got an android app setup for dependency injection using Hilt, and would like to unit test my fragments.
I'm currently creating my view model using:
private val viewModel: ExampleViewModel by viewModels()
And I am creating the fragment for testing using the code from here
I need to replace this ExampleViewModel with a mock, how would I go about doing this?
I will paste here the "danysantiago" response in a issue (https://github.com/google/dagger/issues/1972) related to your question:
Hilt ViewModel extension works by declaring modules that bind assisted
factories to a map and not by binding concrete ViewModels. Therefore,
what you want to do is bind the assisted factory of the concrete
ViewModel using the key of the abstract ViewModel so that when
HiltViewModelFactory looks up the factory based on class key it uses
the assisted factory for the concrete ViewModel. This is suuuper
obscure and hence why I mean not 'easily' available.
However, if you can expand on the test case your are trying to write
that could help us provide some guidance, I'm not sure if you are
trying to mock/fake the ViewModel itself for tests, but Hilt testing
APIs should allow you to replace dependencies in the ViewModel so you
can write a test with the Fragment and the ViewModel.

ViewModelFactory need

I'm doing some codelabs of kotlin fundamentals and I don't really get in android with the ViewModel why sometimes there seems to be a need of creating it through a ViewModelFactory. Here you can see the codelab which talks about this.
They just say to perform the initialization using the factory method pattern but I don't understand the why. Why do we need to use factory pattern? Is it because we need to pass some parameter to the ViewModel? Or is it for some other reason? Is every time we need to create a ViewModelFactory just to pass parameters to the ViewModel?
I've been looking for the answer, trying to confirm whether is just to pass extra parameters or is because of any other reason but still I'm not sure and I haven't found the answer.
There are a few things that need to consider before using ViewModel and ViewModelFactory
ViewModel is LifecycleAware Components.
ViewModel survive configuration changes.
ViewModelProvider' can only instantiateViewModel` with no-args contructor.
Why do we need to use factory pattern?
To instantiate ViewModel with arguments need to use ViewModelFactory. ViewModelProviders Utility can not create an instance of a ViewModel with argument constructor because it does not know how and what objects to pass in the constructor.
Also, you should follow the Dependency Injection principle. A class should not create dependencies it needs. It should be provided rather than creating.
For Example -
public class LogInViewModel extends ViewModel {
private final LogInRepo repo;
public LogInViewModel (LogInRepo repo) {
/* this.repo = new LogInRepo(); Not recommended, It violates DI principle*/
this.repo = repo;
}
}

Android ViewModel reference Fragment

I'm a beginner android dev and am looking to write a fully jetpack app. I'm wondering the best way to communicate with the fragment from the ViewModel to say, show a dialog, or navigate to another fragment.
I had something like this:
class Foo: ViewModel() {
interface Callbacks {
fun doSomethingWithUI()
fun showBaz()
}
var callbacks: Callbacks? = null
}
And then in the fragments onCreate it would set itself as the viewmodels callbacks object.
Looking at the documentation it states that the ViewModel shouldn't hold onto anything related view or lifecycle, or that has a reference to the activity. Thinking on this the ViewModel is referencing the fragment that has the activity.
I'm wondering, why is this bad. Everything seems to be working correctly and if there only ever was one activity to begin with, what would we ever be leaking? Why does google make it very clear not to hold onto these things but never really says why not to.
Also, since what I am doing is not ok, what is the "correct" way for having the ViewModel tell the fragment to say, "show a dialog with this error"
You could use Livedata with an Observer. For example you can use a
Mutablelivedata<Boolean> and attach an Observer to it.
You get the Viewmodel in the Fragment and attach an Observer to the Mutablelivedata<Boolean> which Shows a Dialog when the Mutablelivedata<Boolean> changes to true. An Observer is called when you attach it the first time to the Livedata and when the data changes.
This is very broad topic and you should definitely check architecture samples from google, but in short here is the idea. Let's say you'd like to have some notification channel to report various errors. Then:
Under your view model, you can define private val errorsLiveData: MutableLiveData<Exception> = MutableLiveData()
Under your fragment, you can subscribe to the stream like this:
myViewModel.getErrorsLiveData().observe(viewLifecycleOwner, Observer {
showError(it)
})
How view model can report errors: getErrorsLiveData().value = MyException(it)
You can first create the live data into two parts Mutable and Immutable. The mutable live data will be used inside the viewModel only and the Immutable one can be observed from the fragment or the activity.
class CarViewModel: ViewModel() {
private val repo: EngineRepo() // an instance of your repository inside ViewModel
private val _engine = MutableLiveData<List<Engines>>() //mutable live data to be used inside the viewModel
val : LiveData<List<Engines>> = _engine //this is the observable live data
Now,
fun fetchEngineDetails() {
viewModelScope.launch(Dispatchers.IO){
_engine.postValue(repo.getEngine()) // calling the function inside your repo
}
}
Now you are done with your viewModel
So, to call this ViewModel inside your fragment let's say you can create an instance of ViewModel class.
class CarFragment: Fragment(){
private val carViewModel: CarViewModel by viewModels()
Now inside your onCreate method you can call the function using ViewModel like this:
varViewModel.fetchEngineDetails()
And inside onCreateView you can observe the livedata like this:
carViewModel.engine.observe {(viewlifeCycleOwner)} {
Toast.makeText(requireContext, "Number of engines is {it.size}, Toast.LENGTH_SHORT).show()
}
Please note that to use or create instance of ViewModel class inside the fragment the way I have written here you have to put some dependencies in your gradles
So in you build.gradle (Project)
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
and in your build.gradle(App Module)
inside plugins add:
id 'androidx.navigation.safeargs.kotlin'
and inside dependencies add :
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation "android.arch.lifecycle:extensions:1.1.1"

Categories

Resources