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!
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
getDrawableRes(@DrawableRes int id)
inside the ResourceProvider class –
Beutler For Android Architecture Components View Model,
Edit 1 : It's not a good practice to pass your Activity Context to the Activity's ViewModel as its a memory leak. You shouldn't be need your context this way as there are better ways to write your code and there are better ways to manage the reference to your UI as well with AndroidViewModel.
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
}
application
. Is there any point using getApplication<Application>()
rather than using the application
passed to the ActivityViewModel? Actually both of them are the same application anyway. –
Sidesman ViewModelProvider
doesn't work if we use ViewModel. –
Sidesman 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.
AndroidViewModel
? What do I have to pay attention to to avoid memory leaks or inconsistencies if I use it? –
Keven 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.
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
getDrawableRes(@DrawableRes int id)
inside the ResourceProvider class –
Beutler 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.
As others have mentioned, there's 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 defeats the purpose MVVM.
In general, 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
.
Instead of having 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
. Say, you need different drawables to be displayed in a view for on/off state -- it's the ViewModel
that should hold the (probably boolean) state but it's the View
's business to select the drawable accordingly.
DataBinding makes it quite easy:
<ImageView
...
app:src="@{viewModel.isOn ? @drawable/switch_on : @drawable/switch_off}"
/>
If you have more states and drawables, to avoid unwieldy logic in the layout file you can write a custom BindingAdapter that translates, say, an Enum
value into an R.drawable.*
ref, e.g.:
enum class CatType { NYAN, GRUMPY, LOL }
class CatViewModel {
val catType: LiveData<CatType> = ...
// View-tier logic, takes the burden of knowing
// Contexts and R.** refs from the ViewModel
@BindingAdapter("bindCatImage")
fun bindCatImage(view: ImageView, catType: CatType) = view.apply {
val resource = when (value) {
CatType.NYAN -> R.drawable.cat_nyan
CatType.GRUMPY -> R.drawable.cat_grumpy
CatType.LOL -> R.drawable.cat_lol
}
setImageResource(resource)
}
<ImageView
bindCatType="@{vm.catType}"
... />
If you need the Context
for some component that you use within your ViewModel
-- then, create the component outside the ViewModel
and pass it in. You can use DI, or singletons, or create the Context
-dependent component right before initialising the ViewModel
in Fragment
/Activity
.
Why bother
Context
is an Android-specific thing, and depending on it in ViewModel
s is unwieldy for unit tests (of course you can use AndroidJunitRunner
for android-specific stuff, but it just makes sense to have cleaner code without the extra dependency). If you don't depend on Context
, mocking everything for the ViewModel
test is easier. So, rule of thumb is: don't use Context
in ViewModels unless you have a very good reason to do so.
android.graphics.drawable.Drawable
and set it manually from within your frag/activity code on the binding object. But this sounds like something that defeats the purpose of ViewModels a little bit. If that's something related to some logical state of your view, I'd rather make a sealed class structure representing that state and write a binder which maps the properly typed states to specific drawables. –
She StateFlow<Event<String>>
) and observe it from your Fragment -- that way you don't have to have a Context in your VM. –
She 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.
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.
Hope it helps!
In Hilt:
@Inject constructor(@ApplicationContext context : Context)
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.
This is a way to get Context in to ViewModel
private val context = getApplication<Application>().applicationContext
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
I was having trouble getting SharedPreferences
when using the ViewModel
class so I took the advice from answers above and did the following using AndroidViewModel
. Everything looks great now
For the AndroidViewModel
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.preference.PreferenceManager;
public class HomeViewModel extends AndroidViewModel {
private MutableLiveData<String> some_string;
public HomeViewModel(Application application) {
super(application);
some_string = new MutableLiveData<>();
Context context = getApplication().getApplicationContext();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
some_string.setValue("<your value here>"));
}
}
And in the Fragment
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
public class HomeFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
final View root = inflater.inflate(R.layout.fragment_home, container, false);
HomeViewModel homeViewModel = ViewModelProviders.of(this).get(HomeViewModel.class);
homeViewModel.getAddress().observe(getViewLifecycleOwner(), new Observer<String>() {
@Override
public void onChanged(@Nullable String address) {
}
});
return root;
}
}
Using Hilt
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Singleton
@Provides
fun provideContext(application: Application): Context = application.applicationContext
}
Then pass it via constructor
class MyRepository @Inject constructor(private val context: Context) {
...
}
Finally i got the easiest way to get context in viewModel using MVVM. Suppose we need context in viewmodel class so we can go to dependency injection or using ANDROID_VIEW_MODEL instead of using ViewModel. sample is given below.
class SampleViewModel(app: Application) : AndroidViewModel(app){
private val context = getApplication<Application>().applicationContext
val prefManager = PrefManager(context)
//Now we can call any method which is in PrefManager class like
prefManager.getToken()
}
The Android docs have best practices warning against having references to context in your view models as some others have pointed out.
As they can potentially live longer than the ViewModelStoreOwner, ViewModels shouldn't hold any references of lifecycle-related APIs such as the Context or Resources to prevent memory leaks.
There is other very useful information about the topic on the same page about how to architect your view models.
https://developer.android.com/topic/libraries/architecture/viewmodel#best-practices
Use the following pattern:
class NameViewModel(
val variable:Class,application: Application):AndroidViewModel(application){
body...
}
The problem with injecting a Context into the ViewModel is that the Context can change at any time, depending on screen rotation, night mode, or system language, and any returned resources can change accordingly. Returning a simple resource ID causes problems for extra parameters, like getString substitutions. Returning a high-level result and moving rendering logic to the Activity makes it harder to test.
My solution is to have the ViewModel generate and return a function that is later run through the Activity's Context. Kotlin's syntactic sugar makes this incredibly easy!
ViewModel.kt:
// connectedStatus holds a function that calls Context methods
// `this` can be elided
val connectedStatus = MutableLiveData<Context.() -> String> {
// initial value
this.getString(R.string.connectionStatusWaiting)
}
connectedStatus.postValue {
this.getString(R.string.connectionStatusConnected, brand)
}
Activity.kt // is a Context
override fun onCreate(_: Bundle?) {
connectionViewModel.connectedStatus.observe(this) { it ->
// runs the posted value with the given Context receiver
txtConnectionStatus.text = this.run(it)
}
}
This allows ViewModel to hold all of the logic for calculating the displayed information, verified by unit tests, with the Activity being a very simple representation with no internal logic to hide bugs.
@BindingAdapter("android:text")
fun setText(view: TextView, value: Context.() -> String) {
view.text = view.context.run(value)
}
–
Cyanogen class MainViewModel(
private val mainRepository: MainRepository, private val networkHelper: NetworkHelper, mContext:Context
) : ViewModel() {
private val activityRef:WeakReference<Context> = WeakReference(mContext)
private val newContext:Context?
get() = activityRef.get()
private val _data = MutableLiveData<Resource<YourResponse>>()
val data: LiveData<Resource<YourResponse>>
get() = _data
fun userTypeData(key: String, host:String, data: JsonObject): LiveData<Resource<YourResponse>> {
// define context ref
val currentContext=newContext
viewModelScope.launch {
_data.postValue(Resource.loading(null))
if (networkHelper.isNetworkConnected()) {
mainRepository.getResponse(key,host, data).let {
if (it.isSuccessful) {
_data.postValue(Resource.success(it.body()))
} else {
_data.postValue(Resource.error(it.errorBody().toString(), null))
}
}
} else {
//here you can see I used currentContext
Toast.makeText(currentContext, currentContext!!.getString(R.string.no_internet_connection), Toast.LENGTH_SHORT).show()
_data.postValue(Resource.error(currentContext!!.getString(R.string.no_internet_connection), null))
}
}
return _data
}
}
Through WeakReference
you can pass context and they not return staicfeild
annotation and not memory leaks.
This can be helpful from the Android Developers docs:
The UI logic, particularly when it involves UI types like Context, should live in the UI, not in the ViewModel. If the UI grows in complexity and you want to delegate the UI logic to another class to favor testability and separation of concerns, you can create a simple class as a state holder.
You can find it here.
you can access the application context from getApplication().getApplicationContext()
from within the ViewModel. This is what you need to access resources, preferences, etc..
ViewModel
class does not have the getApplication
method. –
Valentin AndroidViewModel
does –
Helga 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;
© 2022 - 2025 — McMap. All rights reserved.
AndroidViewModel
but gettingCannot create instance exception
then you can refer to my this answer https://mcmap.net/q/129418/-passing-application-to-androidviewmodel – Bigwig