How to get Context in Android MVVM ViewModel

﹥>﹥吖頭↗ 提交于 2020-04-05 07:35:42

问题


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 @drawables 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 Views 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!