问题
I am trying to implement MVVM pattern in my android app. I have read that ViewModels should contain no android specific code (to make testing easier), however I need to use context for various things (getting resources from xml, initializing preferences, etc). What is the best way to do this? I saw that AndroidViewModel
has a reference to the application context, however that contains android specific code so I'm not sure if that should be in the ViewModel. Also those tie into the Activity lifecycle events, but I am using dagger to manage the scope of components so I'm not sure how that would affect it. I am new to the MVVM pattern and Dagger so any help is appreciated!
回答1:
You can use an Application
context which is provided by the AndroidViewModel
, you should extend AndroidViewModel
which is simply a ViewModel
that includes an Application
reference.
回答2:
It's not that ViewModels shouldn't contain Android specific code to make testing easier, since it's the abstraction that makes testing easier.
The reason why ViewModels shouldn't contain an instance of Context or anything like Views or other objects that hold onto a Context is because it has a separate lifecycle than Activities and Fragments.
What I mean by this is, let's say you do a rotation change on your app. This causes your Activity and Fragment to destroy itself so it recreates itself. ViewModel is meant to persist during this state, so there's chances of crashes and other exceptions happening if it's still holding a View or Context to the destroyed Activity.
As for how you should do what you want to do, MVVM and ViewModel works really well with the Databinding component of JetPack. For most things you would typically store a String, int, or etc for, you can use Databinding to make the Views display it directly, thus not needing to store the value inside ViewModel.
But if you don't want Databinding, you can still pass the Context inside the constructor or methods to access the Resources. Just don't hold an instance of that Context inside your ViewModel.
回答3:
For Android Architecture Components View Model,
It's not a good practice to pass your Activity Context to the Activity's ViewModel as its a memory leak.
Hence to get the context in your ViewModel, the ViewModel class should extend the Android View Model Class. That way you can get the context as shown in the example code below.
class ActivityViewModel(application: Application) : AndroidViewModel(application) {
private val context = getApplication<Application>().applicationContext
//... ViewModel methods
}
回答4:
What I ended up doing instead of having a Context directly in the ViewModel, I made provider classes such as ResourceProvider that would give me the resources I need, and I had those provider classes injected into my ViewModel
回答5:
Short answer - Don't do this
Why ?
It defeats the entire purpose of view models
Almost everything you can do in view model can be done in activity/fragment by using LiveData instances and various other recommended approaches.
回答6:
As others have mentioned, theres AndroidViewModel
which you can derive from to get the app Context
but from what I gather in the comments, you're trying to manipulate @drawable
s from within your ViewModel
which almost certainly defeats the purpose of doing the whole MVVM thing.
Altogether, the need to have a Context
in your ViewModel
almost universally suggests you should consider rethinking how you divide the logic between your View
s and ViewModels
.
E.g. instead of having the ViewModel
resolve drawables and feed them to the Activity/Fragment, consider having the Fragment/Activity juggle the drawables based on data possessed by the ViewModel
. E.g., if you have some kind of On/Off indicator, it's the ViewModel
that should hold the (probably boolean) state but it's the View
's business to select the proper drawable accordingly.
In case you need the Context
for some components/services not directly related to view (e.g. backend requests) to the ViewModel
's constructor (manually/by injection) -- that way, no explicit dependency on Context
, and, consequently, easy mocking in tests (just pass mock services/components to constructor or provide them to injecting harness of choice, no need for actual Context
)
回答7:
you can access the application context from getApplication().getApplicationContext()
from within the ViewModel. This is what you need to access resources, preferences, etc..
回答8:
has a reference to the application context, however that contains android specific code
Good news, you can use Mockito.mock(Context.class)
and make the context return whatever you want in tests!
So just use a ViewModel
as you normally would, and give it the ApplicationContext via the ViewModelProviders.Factory as you normally would.
回答9:
The MVVM is a good architecture and It's definitely the future of Android development, but there's a couple of things that are still green. Take for example the layer communication in a MVVM architecture, I've seen different developers (very well known developers) use LiveData to communicate the different layers in different ways. Some of them use LiveData to communicate the ViewModel with the UI, but then they use callback interfaces to communicate with the Repositories, or they have Interactors/UseCases and they use LiveData to communicate with them. Point here, is that not everything is 100% define yet.
That being said, my approach with your specific problem is having an Application's context available through DI to use in my ViewModels to get things like String from my strings.xml
If I'm dealing with image loading, I try to pass through the View objects from the Databinding adapter methods and use the View's context to load the images. Why? because some technologies (for example Glide) can run into issues if you use the Application's context to load images.
TL;DR: Inject the Application's context through Dagger in your ViewModels and use it to load the resources. If you need to load images, pass the View instance through arguments from the Databinding methods and use that View context.
Hope it helps!
回答10:
You should not use Android related objects in your ViewModel as the motive of using a ViewModel is to separate the java code and the Android code so that you can test your business logic separately and you will have a separate layer of Android components and your business logic and data ,You should not have context in your ViewModel as it may lead to crashes
回答11:
I created it this way:
@Module
public class ContextModule {
@Singleton
@Provides
@Named("AppContext")
public Context provideContext(Application application) {
return application.getApplicationContext();
}
}
And then I just added in AppComponent the ContextModule.class:
@Component(
modules = {
...
ContextModule.class
}
)
public interface AppComponent extends AndroidInjector<BaseApplication> {
.....
}
And then I injected the context in my ViewModel:
@Inject
@Named("AppContext")
Context context;
回答12:
Use the following pattern:
class NameViewModel(
val variable:Class,application: Application):AndroidViewModel(application){
body...
}
来源:https://stackoverflow.com/questions/51451819/how-to-get-context-in-android-mvvm-viewmodel