How to get Context in Android MVVM ViewModel
Asked Answered
B

21

203

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!

Beutler answered 21/7, 2018 at 0:29 Comment(4)
Just in case someone is trying to use AndroidViewModel but getting Cannot create instance exception then you can refer to my this answer https://mcmap.net/q/129418/-passing-application-to-androidviewmodelBigwig
You shouldn't use Context in a ViewModel, create a UseCase instead to get the Context from that wayDrislane
@RubenCaster do you have any sample or GitHub link for that?Eyehole
@Eyehole No, sorry. Its a private project =(Drislane
B
31

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

Beutler answered 16/8, 2018 at 4:6 Comment(9)
I'm using ResourcesProvider with Dagger in AppModule. Is that good approach to get the context fro ResourcesProvider or AndroidViewModel is better to get context for resources?Moisten
@Vincent : How to use resourceProvider to get Drawable inside ViewModel?Cartography
@Vegeta You would add a method like getDrawableRes(@DrawableRes int id) inside the ResourceProvider classBeutler
This goes against Clean Architecture approach which states that framework dependencies should not cross boundaries into domain logic (ViewModels).Fermata
@Fermata VMs aren't exactly domain logic. Domain logic are other classes such as interactors and repositories to name a few. VMs fall into the "glue" category since they do interact with your domain, but not directly though. If your VMs are part of your domain then you should reconsider how you're using the pattern since you're giving them too much responsibility.Fissionable
@VincentWilliams Could you please clarify - so ResourceProvider has reference to Context and ViewModel in it's turn has ref to the ResourceProvider, how is that different from having context ref in ViewModel?Infirm
@VincentWilliams I've done this in dagger2. But how do you do this in hilt?Follansbee
That's how I do it.. and also I keep my ViewModel with a simple constructor away from explicitly adding dependency injection. I have a ResourceProvider as an interface and a ResourceProviderImpl which backs it up with context based on the Application. This is the best way to also involve unit testing.Bryant
@VincentWilliams You can do this in a Dagger module which can declare and return an instance of your viewModel. The method involve in providing it must also have params of the requirements for the view model, and inside the method simply create it and add the parameters. Very Simple and friendly when it involves just unit testing the viewmodel with mocked parameters.Bryant
F
136

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 

}
Figurative answered 7/6, 2019 at 8:17 Comment(9)
Why not directly use the application parameter and a normal ViewModel? I see no point in "getApplication<Application>()". It just adds boilerplate.Relive
Why would it be a memory leak?Cureall
Oh i see, because an activity will be destroyed more often than its view model (e.g. when screen is rotating). Unfortunately, the memory won't be released by the garbage collection because the view model still has a reference to it.Cureall
Quick question: We can just use the variable 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
@TheincredibleJan I tried it, but it doesn't work. Somehow the ViewModel cannot be instantiated. But it works if we use the AndroidViewModel, instead of ViewModel. I guess the dependency injection by ViewModelProvider doesn't work if we use ViewModel.Sidesman
Anyway, keeping a reference to the old activity in the ViewModel is not just a memory leak, its just buggy code. When ViewModel calls its activity reference, it might have already been killed ages ago → CRASH. So don't store activities in ViewModels because the activity has a different lifecycle. @Jackey's answer is better than this one.Cureall
This is definitely a memory leak.Fermata
We can nomrally use val context = application, which is declared in the ActivityViewModel's constructor and use that context variable where you need in the viewmodel.it will works prefectly finePyrex
no one is talking about Activity Context, it's about App Context!Palpebrate
H
123

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.

Haematin answered 30/10, 2018 at 1:35 Comment(1)
But is it good practice to use AndroidViewModel? What do I have to pay attention to to avoid memory leaks or inconsistencies if I use it?Keven
R
77

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.

Rosannarosanne answered 21/7, 2018 at 0:41 Comment(7)
It was my understanding that including android-specific code required instrumentation tests to be run which is much slower than plain JUnit tests. I am currently using Databinding for click methods but I don't see how it would help with get resources from xml or for preferences. I just realized that for preferences, I would need a context inside of my model as well. What I am currently doing is having Dagger inject the application context (the context module gets it from a static method inside the application class)Beutler
@VincentWilliams Yes, using a ViewModel helps abstract your code away from your UI components, which makes it easier for you to conduct testing. But, what I'm saying is that the primary reason for not including any Context, Views, or the like isn't because of testing reasons, but because of the ViewModel's lifecycle which can help you avoid crashes and other errors. As for databinding, this can help you with resources because most of the time you need to access the resources in code is due to needing to apply that String, color, dimen into your layout, which databinding can do directly.Rosannarosanne
Oh ok I see what you mean but databinding will not help me in this case since I need to access strings for use in the model (These could be put in a constants class instead of xml I suppose) and also for initializing SharedPreferencesBeutler
if i want to toggle text in a textview based on a value form viewmodel ,the string need to be localized ,so i need get resources in my viewmodel ,without context how will i access the resources?Franko
@SrishtiRoy If you use databinding, it's easily possible to toggle a TextView's text based on the value from your viewmodel. There is no need for access to a Context inside your ViewModel because all of this happens within the layout files. However, if you must use a Context within your ViewModel, then you should consider using the AndroidViewModel instead of ViewModel. AndroidViewModel contains the Application Context which you can call with getApplication(), so that should satisfy your Context needs if your ViewModel requires a context.Rosannarosanne
@Jackey, The main purpose of ViewModel is lost if Views need to be recreated. ViewModel should be fixed to allow us to keep references to Views and refresh them with the new Activity.Down
@Down You've misunderstood the main purpose of ViewModel. It's a separation of concerns issue. The ViewModel should not keep references to any views, since it's responsibility is to maintain the data that's being displayed by the View layer. The UI components, aka views, is maintained by the View layer and the Android System will recreate the Views if it's necessary. Keeping a reference to old Views will conflict with this behavior and cause memory leaks.Rosannarosanne
B
31

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

Beutler answered 16/8, 2018 at 4:6 Comment(9)
I'm using ResourcesProvider with Dagger in AppModule. Is that good approach to get the context fro ResourcesProvider or AndroidViewModel is better to get context for resources?Moisten
@Vincent : How to use resourceProvider to get Drawable inside ViewModel?Cartography
@Vegeta You would add a method like getDrawableRes(@DrawableRes int id) inside the ResourceProvider classBeutler
This goes against Clean Architecture approach which states that framework dependencies should not cross boundaries into domain logic (ViewModels).Fermata
@Fermata VMs aren't exactly domain logic. Domain logic are other classes such as interactors and repositories to name a few. VMs fall into the "glue" category since they do interact with your domain, but not directly though. If your VMs are part of your domain then you should reconsider how you're using the pattern since you're giving them too much responsibility.Fissionable
@VincentWilliams Could you please clarify - so ResourceProvider has reference to Context and ViewModel in it's turn has ref to the ResourceProvider, how is that different from having context ref in ViewModel?Infirm
@VincentWilliams I've done this in dagger2. But how do you do this in hilt?Follansbee
That's how I do it.. and also I keep my ViewModel with a simple constructor away from explicitly adding dependency injection. I have a ResourceProvider as an interface and a ResourceProviderImpl which backs it up with context based on the Application. This is the best way to also involve unit testing.Bryant
@VincentWilliams You can do this in a Dagger module which can declare and return an instance of your viewModel. The method involve in providing it must also have params of the requirements for the view model, and inside the method simply create it and add the parameters. Very Simple and friendly when it involves just unit testing the viewmodel with mocked parameters.Bryant
S
25

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.

Sorilda answered 25/7, 2019 at 13:38 Comment(9)
Why then AndroidViewModel class even exists?Allergy
@AlexBerdnikov The purpose of MVVM is to isolate the view(Activity/Fragment) from ViewModel even more than MVP. So that it will be easier to test.Corsica
@free_style Thanks for clarification, but the question still stands: if we must not keep context in ViewModel, why AndroidViewModel class even exists? Its entire purpose is to provide application context, isn't it?Allergy
@AlexBerdnikov Using Activity context inside the viewmodel can cause memory leaks. So by using AndroidViewModel Class you will be provided by Application Context which wont (hopefully) be causing any memory leak. So using AndroidViewModel might be better than passing activity context to it. But still doing so will make the testing difficult. This is my take on it.Corsica
@AlexBerdnikov purpose of ViewModel class is to store your activity data, so that it can be used by activity (or its fragments) in a much reliable way. developer.android.com/topic/libraries/architecture/viewmodelSorilda
I can't access file from res/raw folder from repository?Johiah
please note that this is not an activity context but application context, there is only one application running, which gets destroyes when the app ends, so there is no possibility of memory leak in this specific caseRotterdam
Android ViewModels are NOT "view models". Their purpose is to make up for the architectural gaps of Activities/Fragments losing their state upon rotation/configuration changes, which has been a fallacy on Android since its inception. Treating them as if they were an INTENTIONAL separate abstraction only adds a bunch of extra complexity with zero real-world benefit (in fact the opposite). Activity + Fragment + ViewModel together in Android act as a "controller". There is no good reason why ViewModel should be isolated from a Context. It IS logically an Activity/Fragment.Figone
@Rotterdam Careful with that. It is an application context so config changes or activity specific themes etc will not be properly reflected in that context.Donadonadee
S
24

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

She answered 18/7, 2019 at 12:7 Comment(5)
Good afternoon. I want to clarify with you about how it is possible to implement the display of confirmation when deleting a record from the room repository without using the context? I need context when I create alert dialog. Thank you.Geibel
This is a great answer - is there a way to feed some data into my binding adapter from an activity or fragment? I have some drawables but I use the context to get them, hence me being here!Pint
@Pint You mean like how? I suppose you could declare a variable in your layout with type 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
@Geibel Sorry for the late reply. You could expose the event of showing the alert (e.g. as a StateFlow<Event<String>>) and observe it from your Fragment -- that way you don't have to have a Context in your VM.She
Good approach, thanksDonadonadee
M
14

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!

Methodize answered 21/7, 2018 at 3:2 Comment(2)
TL;DR should be at the topCaddric
Thank you for your answer. However, why would you use dagger to inject the context if you could make your viewmodel extend from androidviewmodel and use the builtin context the class itself provides? Especially considering the ridiculous amount of boilerplate code to make dagger and MVVM work together the other solution seems much clearer imo. What are your thoughts on this?Demoralize
F
12

In Hilt:

@Inject constructor(@ApplicationContext context : Context) 
Flickinger answered 30/9, 2021 at 9:5 Comment(2)
I'm a Dagger/Hilt n00b, and am confused on how this works. In my app's DI module, I have "provides" functions for different things, like Repositories, Room Database, etc. Each of these "provides" takes something in a constructor that's needed to create that object that's being returned. You can trace the source of each thing passed in the constructors all the way up to "Application" (which there's an app class that extends it, I guess that's "root"). I guess I'm just not sure how it comes up with the Context/ApplicationContext in this case. You don't need a "provides" function to go with this?Phthalein
@Phthalein in Hilt it works this wayMcclendon
S
6

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.

Schoonmaker answered 21/7, 2018 at 14:31 Comment(0)
F
6

This is a way to get Context in to ViewModel

private val context = getApplication<Application>().applicationContext
Ferroconcrete answered 19/8, 2022 at 13:11 Comment(0)
P
4

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

Povertystricken answered 21/7, 2018 at 6:16 Comment(2)
This is a fair observation, but some of the backend libraries still require Application contexts, such as MediaStore. The answer by 4gus71n below explains how to compromise.Attack
Yes You Can Use Application Context But Not the Context of Activities ,As the Application Context lives throughout the application life cycle but not the Activity Context as Passing Activity Context to any asynchronous process can result in memory leaks.The Context mentioned in my post is Activity Context.But You Should Still take Care of not passing context to any asynchronous process even if it is applications context.Povertystricken
W
4

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;
    }
}
Waylan answered 20/6, 2020 at 22:26 Comment(0)
C
4

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) {
...
}
Cervantez answered 24/2, 2021 at 11:54 Comment(1)
How is Hilt even relevant? It is not like Hilt magically provides the context, you could have done that without Hilt as wellOutdate
B
1

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()

}
Biogeography answered 13/1, 2022 at 6:45 Comment(1)
prefManager should have been provided to ViewModel via dependency injectionPrenatal
M
1

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

Mississippian answered 10/7, 2023 at 15:47 Comment(0)
L
0

Use the following pattern:

class NameViewModel(
val variable:Class,application: Application):AndroidViewModel(application){
   body...
}
Linhliniment answered 3/11, 2019 at 7:31 Comment(1)
Short, simple, preciseGesualdo
C
0

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.

Cyanogen answered 28/11, 2020 at 17:34 Comment(1)
And to enable databinding support, you just add a simple BindingAdapter like so: @BindingAdapter("android:text") fun setText(view: TextView, value: Context.() -> String) { view.text = view.context.run(value) }Cyanogen
B
0
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.

Bouncing answered 1/8, 2023 at 11:48 Comment(3)
How does WeakReference prevent memory leaks?Input
Weak references do not prevent the object they point to from being garbage collected. If an object is only referenced weakly, and there are no strong references to it, it becomes eligible for garbage collection. When the garbage collector runs, it identifies objects that are no longer strongly referenced (i.e., not reachable through strong references) and can safely collect them, freeing up memory resources.Bouncing
This allows you to manage objects that should only be retained as long as they are actively needed, such as cached data, event listeners, or temporary objects, without worrying about holding them in memory indefinitely.Bouncing
B
0

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.

Bilbao answered 25/4, 2024 at 20:10 Comment(0)
L
-1

you can access the application context from getApplication().getApplicationContext() from within the ViewModel. This is what you need to access resources, preferences, etc..

Lenette answered 21/7, 2018 at 0:40 Comment(5)
I guess to narrow my question down. Is it bad to have a context reference inside the viewmodel (doesn't this affect testing?) and would using the AndroidViewModel class affect Dagger in any way? Isn't it tied to the activity lifecycle? I am using Dagger to control the lifecycle of componentsBeutler
The ViewModel class does not have the getApplication method.Valentin
No, but AndroidViewModel doesHelga
But you need to pass the Application instance in its constructor, it's just the same as accessing the Application instance from itProbative
It doesn't make a big problem to have application context. You don't want to have an activity/fragment context because you're borked if the fragment/activity is destroyed and the view model still has a reference to the now nonexistent context. But you're never going to have APPLICATION context destroyed but the VM still has a reference to it. Right? Can you imagine a scenario where your app exits but the Viewmodel doesn't? :)Malti
B
-1

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;
Bedplate answered 8/8, 2019 at 11:12 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.