I would like to make a paper example MVVM Android project so I manually inject my viewmodel and release it in the end.
I am making everything as a singleton like this:
companion object {
private var instance: PlantScreenViewModel? = null
fun getInstance(plantScreenInterface: PlantScreenInterface) =
instance
?: PlantScreenViewModel(plantScreenInterface = plantScreenInterface).apply {
instance = this
}
}
And injecting like this:
plantScreenViewModel =
PlantScreenViewModel.getInstance(
plantScreenInterface = PlantScreenFirebaseImpl.getInstance()
)
and in the onDestroy method i do this:
override fun onDestroy() {
super.onDestroy()
if (!isChangingConfigurations) {
plantScreenViewModel.clear()
}
}
and the clear methis in the viewmodel looks like this:
fun clear() {
onCleared()
instance = null
}
I am trying to debug the code as much as i can and i see no leak so far:
The viewmodel only holds one LiveData which is freed in my opinion because the onDestroy() method was called and i am changing the instance back to null.
Am i doing anything wrong?
PS.: I DO NOT want to use Dagger Hilt since i would like to create everything by myself.
I am aware of ViewmodelProvider and i always use that or other viewmodel injecting function but this is just a small home project and i am just testing the water.
Related
I'm developing a huge section of my Android app in Jetpack Compose with the MVVM pattern.
I have a ViewModel father that is extended by all the other ViewModels. There, I have defined an open function which contains the initialization logic of each ViewModel that I need to call every time I enter in a new screen and to call again when something went wrong and the user clicks on the "try again" button.
abstract class MyFatherViewModel(): ViewModel() {
open fun myInitMethod() {}
fun onTryAgainClick() {
myInitMethod()
}
}
class MyScreen1ViewModel(): MyFatherViewModel() {
init {
myInitMethod()
}
override fun myInitMethod() {
super.myInitMethod()
// Do something
}
}
class MyScreen2ViewModel(): MyFatherViewModel() {
init {
myInitMethod()
}
override fun myInitMethod() {
super.myInitMethod()
// Do something
}
}
Is there a way I can call this method in the init function of MyFatherViewModel instead of doing it in all the children ViewModels? If I try to do that, it gives me the "Calling non-final function in constructor" warning and, of course, it doesn't work.
abstract class MyFatherViewModel(): ViewModel() {
open fun myInitMethod() {}
init {
myInitMethod()
}
fun onTryAgainClick() {
myInitMethod()
}
}
Is it possible to call a non-final function in constructor?
Technically yes, but you shouldn't. Kotlin is trying to protect you from problems here. If you call an open function from a constructor, it means you are running code from the child class before the parent class is completely initialized, and before the child class even started initializing. If the child implementation of the open function tries to access properties from the child class, unexpected things may happen. For instance, non-nullable properties could yield null (because not initialized yet), or primitive values could yield their type's default instead of the default value from their initializer:
fun main() {
Child()
}
open class Parent {
init {
initialize()
}
val id = 42
open fun initialize() = println("parent init")
}
class Child : Parent() {
val name = "Bob"
override fun initialize() = println("initializing $name, parent id=$id")
}
This prints the following output:
initializing null, parent id=0
I guess you can see why this is dangerous.
Maybe you should reconsider what you're trying to do with this try-again feature. Maybe a new view model should be instantiated instead (if try-again is to handle crashes, the state of the current view model may actually be bad enough to want to re-create it from scratch anyway).
I have a repository where a chain of network requests is calling. The repository is accessed from the interactor. And interactor is accessed from viewModel. The view model is attached to activity A. If I go to activity B, which has its own viewModel, then the request chain in the repository of activity A does not complete its execution.
Is it possible to make a repository whose life cycle will be equal to the life cycle of the application. I need all requests to complete even if I go to a new activity.
Please, help me.
This is covered in the Coroutines guide on developer.android.com
class ArticlesRepository(
private val articlesDataSource: ArticlesDataSource,
private val externalScope: CoroutineScope,
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
// As we want to complete bookmarking the article even if the user moves
// away from the screen, the work is done creating a new coroutine
// from an external scope
suspend fun bookmarkArticle(article: Article) {
externalScope.launch(defaultDispatcher) {
articlesDataSource.bookmarkArticle(article)
}
.join() // Wait for the coroutine to complete
}
}
Here externalScope is defined like this (for a scope with application lifetime):
class MyApplication : Application() {
// No need to cancel this scope as it'll be torn down with the process
val applicationScope = CoroutineScope(SupervisorJob() + otherConfig)
}
You should create a singleton Repository class, then access its instance anywhere you want.
object Repository {
val instance: Repository
get() {
return this
}
}
You can create create its object in ViewModel for A and create object in ViewModel for B.
Both will have same instance for Repository class, so in this way you can achieve what you need.
There is a lot of information out there on architecture components, kotlin and coroutines but nowhere I can find an example using all those things together.
I'm struggling on how to use android's architecture components as described here together with coroutines. I have an idea but feel uncertain if it's the correct way of implementating this architectural style.
I'm trying to use the view model + repository pattern together with retro fit and coroutines.
I have the following repository:
class FooRepostiroy(private val fooHttpService: FooHttpService) {
suspend fun someMethod() : SomeResult {
val response = fooHttpService.someRemotCall() // which is also a suspending method using retrofit-2
// process response, store it using room and return SomeResult data object
Then I use the FooRepository from my ViewModel but because someMethod is a suspending method I need to wrap it in a coroutine scope:
class FooViewModel(private val fooRepositoru : FooRepository) : ViewModel() {
private var someMethodJob : Job? = null
val result : MutableLiveData<SomeResult> = MutableLiveData()
fun someMethod() {
someMethodJob = viewModelScope.launch {
result.value = fooRepositoru.someMethod()
}
}
override fun onCleared() {
super.onCleared()
someMethodJob?.cancel()
}
Then in the fragment or activity I can observe the view model result
fooViewModel.result.observe(viewLifecycleOwner, Observer {
Starting from my repository layer and below everything can be a suspending function. Then from the view model I can call any suspending function but never have a publicly exposed suspending function in my view model.
Is this the correct or proper way to incorporate coroutines with the view model architecture ?
Is this the correct or proper way to incorporate coroutines with the view model architecture?
Yes!
Every instance of ViewModel has its own ViewModelScope.
The purpose of ViewModelScope is to run the jobs during the life cycle of that ViewModel and take care of automatic cancelation of running coroutine jobs in case the parent Activity/Fragment of ViewModel is destroyed.
Any running jobs under ViewModelScope will be canceled when the ViewModel will be destroyed.
Read more here
private var someMethodJob : Job? = null
val result : MutableLiveData<SomeResult> = MutableLiveData()
fun someMethod() {
someMethodJob = viewModelScope.launch {
result.value = fooRepositoru.someMethod()
}
}
You can ditch all of that and just say
val result: LiveData<SomeResult> = liveData {
emit(fooRepository.someMethod())
}
And then observe result.
I've built a Splash Screen using Android Architecture Components and Reactive approach.
I return from Preferences LiveData object fun isFirstLaunchLD(): SharedPreferencesLiveData<Boolean>.
I have ViewModel that passes LiveData to the view and updates Preferences
val isFirstLaunch = Transformations.map(preferences.isFirstLaunchLD()) { isFirstLaunch ->
if (isFirstLaunch) {
preferences.isFirstLaunch = false
}
isFirstLaunch
}
In my Fragment, I observe LiveData from ViewModel
viewModel.isFirstLaunch.observe(this, Observer { isFirstLaunch ->
if (isFirstLaunch) {
animationView.playAnimation()
} else {
navigateNext()
}
})
I would like to test my ViewModel now to see if isFirstLaunch is updated properly. How can I test it? Have I separated all layers correctly? What kind of tests would you write on this sample code?
Have I separated all layers correctly?
The layers seem reasonably separated. The logic is in the ViewModel and you're not referring to storing Android Views/Fragments/Activities in the ViewModel.
What kind of tests would you write on this sample code?
When testing your ViewModel you can write instrumentation or pure unit tests on this code. For unit testing, you might need to figure out how to make a test double for preferences, so that you can focus on the isFirstLaunch/map behavior. An easy way to do that is passing a fake preference test double into the ViewModel.
How can I test it?
I wrote a little blurb on testing LiveData Transformations, read on!
Testing LiveData Transformations
Tl;DR You can test LiveData transformation, you just need to make sure the result LiveData of the Transformation is observed.
Fact 1: LiveData doesn't emit data if it's not observed. LiveData's "lifecycle awareness" is all about avoiding extra work. LiveData knows what lifecycle state it's observers (usually Activities/Fragments) are in. This allows LiveData to know if it's being observed by anything actually on-screen. If LiveData aren't observed or if their observers are off-screen, the observers are not triggered (an observer's onChanged method isn't called). This is useful because it keeps you from doing extra work "updating/displaying" an off-screen Fragment, for example.
Fact 2: LiveData generated by Transformations must be observed for the transformation to trigger. For Transformation to be triggered, the result LiveData (in this case, isFirstLaunch) must be observed. Again, without observation, the LiveData observers aren't triggered, and neither are the transformations.
When you're unit testing a ViewModel, you shouldn't have or need access to a Fragment/Activity. If you can't set up an observer the normal way, how do you unit test?
Fact 3: In your tests, you don't need a LifecycleOwner to observe LiveData, you can use observeForever You do not need a lifecycle observer to be able to test LiveData. This is confusing because generally outside of tests (ie in your production code), you'll use a LifecycleObserver like an Activity or Fragment.
In tests you can use the LiveData method observeForever() to observer without a lifecycle owner. This observer is "always" observing and doesn't have a concept of on/off screen since there's no LifecycleOwner. You must therefore manually remove the observer using removeObserver(observer).
Putting this all together, you can use observeForever to test your Transformations code:
class ViewModelTest {
// Executes each task synchronously using Architecture Components.
// For tests and required for LiveData to function deterministically!
#get:Rule
val rule = InstantTaskExecutorRule()
#Test
fun isFirstLaunchTest() {
// Create observer - no need for it to do anything!
val observer = Observer<Boolean> {}
try {
// Sets up the state you're testing for in the VM
// This affects the INPUT LiveData of the transformation
viewModel.someMethodThatAffectsFirstLaunchLiveData()
// Observe the OUTPUT LiveData forever
// Even though the observer itself doesn't do anything
// it ensures any map functions needed to calculate
// isFirstLaunch will be run.
viewModel.isFirstLaunch.observeForever(observer)
assertEquals(viewModel.isFirstLaunch.value, true)
} finally {
// Whatever happens, don't forget to remove the observer!
viewModel.isFirstLaunch.removeObserver(observer)
}
}
}
A few notes:
You need to use InstantTaskExecutorRule() to get your LiveData updates to execute synchronously. You'll need the androidx.arch.core:core-testing:<current-version> to use this rule.
While you'll often see observeForever in test code, it also sometimes makes its way into production code. Just keep in mind that when you're using observeForever in production code, you lose the benefits of lifecycle awareness. You must also make sure not to forget to remove the observer!
Finally, if you're writing a lot of these tests, the try, observe-catch-remove-code can get tedious. If you're using Kotlin, you can make an extension function that will simplify the code and avoid the possibility of forgetting to remove the observer. There are two options:
Option 1
/**
* Observes a [LiveData] until the `block` is done executing.
*/
fun <T> LiveData<T>.observeForTesting(block: () -> Unit) {
val observer = Observer<T> { }
try {
observeForever(observer)
block()
} finally {
removeObserver(observer)
}
}
Which would make the test look like:
class ViewModelTest {
#get:Rule
val rule = InstantTaskExecutorRule()
#Test
fun isFirstLaunchTest() {
viewModel.someMethodThatAffectsFirstLaunchLiveData()
// observeForTesting using the OUTPUT livedata
viewModel.isFirstLaunch.observeForTesting {
assertEquals(viewModel.isFirstLaunch.value, true)
}
}
}
Option 2
#VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this#getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
try {
afterObserve.invoke()
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}
} finally {
this.removeObserver(observer)
}
#Suppress("UNCHECKED_CAST")
return data as T
}
Which would make the test look like:
class ViewModelTest {
#get:Rule
val rule = InstantTaskExecutorRule()
#Test
fun isFirstLaunchTest() {
viewModel.someMethodThatAffectsFirstLaunchLiveData()
// getOrAwaitValue using the OUTPUT livedata
assertEquals(viewModel.isFirstLaunch.getOrAwaitValue(), true)
}
}
These options were both taken from the reactive branch of Architecture Blueprints.
It depends on what your SharedPreferencesLiveData does.
If the SharedPreferencesLiveData contains Android specific classes, you won't be able to test this correctly because JUnit won't have access to the Android specific classes.
The other issue is that to be able to observe LiveData, you need some kind of Lifecycle owner. (The this in the original post code.)
In the Unit test, the 'this' can simply be replaced with something like the following:
private fun lifecycle(): Lifecycle {
val lifecycle = LifecycleRegistry(Mockito.mock(LifecycleOwner::class.java))
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
return lifecycle
}
And then used in the following way:
#RunWith(MockitoJUnitRunner::class)
class ViewModelTest {
#Rule
#JvmField
val liveDataImmediateRule = InstantTaskExecutorRule()
#Test
fun viewModelShouldLoadAttributeForConsent() {
var isLaunchedEvent: Boolean = False
// Pseudo code - Create ViewModel
viewModel.isFirstLaunch.observe(lifecycle(), Observer { isLaunchedEvent = it } )
assertEquals(true, isLaunchedEvent)
}
private fun lifecycle(): Lifecycle {
val lifecycle = LifecycleRegistry(Mockito.mock(LifecycleOwner::class.java))
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
return lifecycle
}
}
Note: You have to have the Rule present so that the LiveData executes instantly instead of whenever it wants to.
I just started using MVVM architecture on Android. I have a service which basically fetches some data and updates the UI and this is what I understood from MVVM:
Activity should not know anything about the data and should take care of the views
ViewModels should not know about activity
Repository is responsible for getting the data
Now as ViewModels should not know anything about the activity and Activities should not do anything other than handling views, Can anyone please tell where should I start a service?
In MVVM, ideally, the methods to start a service should be defined in Repository since it has the responsibility to interact with Data Source. ViewModel keeps an instance of Repository and is responsible for calling the Repository methods and updating its own LiveData which could be a member of ViewModel. View keeps an instance of ViewModel and it observes LiveData of ViewModel and makes changes to UI accordingly. Here is some pseudo-code to give you a better picture.
class SampleRepository {
fun getInstance(): SampleRepository {
// return instance of SampleRepository
}
fun getDataFromService(): LiveData<Type> {
// start some service and return LiveData
}
}
class SampleViewModel {
private val sampleRepository = SampleRepository.getInstance()
private var sampleLiveData = MutableLiveData<Type>()
// getter for sampleLiveData
fun getSampleLiveData(): LiveData<Type> = sampleLiveData
fun startService() {
sampleLiveData.postValue(sampleRepository.getDataFromService())
}
}
class SampleView {
private var sampleViewModel: SampleViewModel
// for activities, this sampleMethod is often their onCreate() method
fun sampleMethod() {
// instantiate sampleViewModel
sampleViewModel = ViewModelProviders.of(this).get(SampleViewModel::class.java)
// observe LiveData of sampleViewModel
sampleViewModel.getSampleLiveData().observe(viewLifecycleOwner, Observer<Type> { newData ->
// update UI here using newData
}
}
As far as I know, Services are Android related so, they could be started from View (Activity/Fragment/Lifecycleowner).