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.
I am using Companion object in service to expose my LiveData to a fragment. Is this okay to use or will it cause me problems like memory leaks?
In my service:
companion object {
val timeLeftInSeconds = MutableLiveData<Long>(0)}
In my fragment:
LockoutService.timeLeftInSeconds.observe(viewLifecycleOwner, Observer {...})
No it's fine because companion object is kinda like static fields, but I highly recommend to use a repository instead because it will increase you code readability and makes it more robust. Something like
object AppRepository{
val timeLeftInSeconds = MutableLiveData<Long>(0)}
}
And in fragment
AppRepository.timeLeftInSeconds.observe(viewLifecycleOwner
No it's totally alright because companion objects are like static properties in java and are not bound to the class you define them in.
Also you can put it in the same file, outside of your service
LockoutService.kt
val timeLeftInSeconds = MutableLiveData<Long>(0)}
class LockoutService {...}
And access it without mentioning the service name
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;
}
}
I am having a DB populated with weather data, in both imperial and metric units. Now, I have made two different classes which act as a model to get data from the DB. CurrentWeatherMetric has only metric columns, and CurrentWeatherImperial has only imperial fields.
Since I am using MVVM architecture pattern, the ViewModel provides me this data, by calling a function in ViewModel getData(Unit.METRIC) where Unit is an enum class I've made to distinguish the data.
The problem arises here.
My viewModel looks like:
class WeatherViewModel(
private val weatherRepository: WeatherRepositoryImpl
) : ViewModel() {
lateinit var currentWeather: LiveData<CurrentWeather>
lateinit var forecastWeather: LiveData<List<ForecastWeather>>
fun getValuesOfUnit(unit: Unit) {
currentWeather = when (unit) {
Unit.IMPERIAL->weatherRepository.getCurrentWeatherImperial()
Unit.METRIC->weatherRepository.getCurrentWeatherMetric()
}
getWeather()
}
private fun getWeather() {
viewModelScope.launch {
try {
weatherRepository.getWeather()
} catch (e: IOException) {
}
}
}
}
As you can see, lateinit var currentWeather: LiveData<CurrentWeather>,
I had to make another class which store the data of the query with units. I made this so that I could easily implement databinding with it. But I feel this is a really wrong way to do things and hence I have asked this question. How can I get rid of that lateinit variable and implement databinding to adapt to any of the data.
In my current data binding layout, I have data field as:
<data>
<variable
name="viewModel"
type="com.mythio.weather.ui.WeatherViewModel" />
</data>
And I bind to views by:
app:someattribute="#{viewModel.currentWeather.temperature}"
If the question title makes a little sense about what I am asking, or seems misleading, please feel free to edit this to make it a better question.
When using MVVM architecture pattern, Google's recommended way is to make ViewModel that handles connection between your data and view, so it contains UI logic as well as some portion of business logic bound to your UI.
Moreover, implementation of ViewModel in recommended way helps you handle UI lifecycle (Activity/Fragments) in better and hassle-free way.
When using data-binding with MVVM, it's good practice to bind ViewModel directly to xml so that, when data changes you can directly reflect it to UI using LiveData without wiring it manually.
Hence, LiveData can be used as Data-Value holder as it's also Lifecycle-aware component.
On the other hand, Repositories are good way to manage business logic and providing "single source of truth" for data driving through app. So, all data sources like local-db, API calls, shared-preferences etc. should be accessed via repository.
So, yes!! Things you're doing are good & you're on the right track while following MVVM Architecture Pattern.
Note: You can refer here for more info and some improvements in your code.
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!
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.
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
}
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
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 #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.
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.
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) {
...
}
This is a way to get Context in to ViewModel
private val context = getApplication<Application>().applicationContext
you can access the application context from getApplication().getApplicationContext() from within the ViewModel. This is what you need to access resources, preferences, etc..
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()
}
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.
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;