scoped injection with koin 3 - android

I am trying to upgrade my koin usage from 2.1.6 -> 3.0.2 and am having troubles with the scoped injections.
I have MVPs where the Activity/Fragment is the view and i want to inject the view in the presenter.
so i have
module {
scope(named<MainActivity>()) {
scoped<View> { getSource() }
scoped<Presenter> {
MainPresenter(
view = get()
)
}
}
in 2.1.6 i used to used to do this and all was fine:
class MainActivity :
AppCompatActivity(),
MainContract.View {
private val presenter: MainContract.Presenter by currentScope.inject()
...
}
and then in MainActivity i NOW have:
class MainActivity :
AppCompatActivity(),
MainContract.View,
AndroidScopeComponent {
override val scope : Scope by activityScope()
private val presenter: MainContract.Presenter by scope.inject()
...
}
and Presenter:
class MainPresenter(
private val view: MainContract.View
){
...
}
but it cannot get the source object and i get the error:
Instance creation error : could not create instance for [Single:'uk.co.sentinelweb.cuer.app.ui.main.MainContract$View',scope:q:'uk.co.sentinelweb.cuer.app.ui.main.MainActivity']: java.lang.IllegalStateException: Can't use Scope source for uk.co.sentinelweb.cuer.app.ui.main.MainContract$View - source is:null
(i.e. when it tries to create the presenter it can't find the scoped MainActivity)
this is the existing code (using 2.1.6)
https://github.com/sentinelweb/cuer/blob/develop/app/src/main/java/uk/co/sentinelweb/cuer/app/ui/main/MainActivity.kt
Have i got a lot more re-writing to do here? I am struggling to find a good example for scoped injection in the koin docs and a lot of it seems old. A lot of projects seem not to use scoping.
So if anyone can tell me what wrong here or point me to a decent example of something similar id appreciate it a lot!

So it seems for the lifecycle aware extension methods it just doesnt set the scope variable - perhaps they are paranoid about mempory leaks but since the scope is cleared and on the destroy lifecycle method - this shouldnt be a problem really.
My solution was to just make new extension methods which actually just pass in the source - I am not sure why this would be a problem. There is an issue here for it https://github.com/InsertKoinIO/koin/issues/851
package xxx
import android.app.Service
import androidx.activity.ComponentActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import org.koin.android.scope.AndroidScopeComponent
import org.koin.android.scope.createScope
import org.koin.android.scope.getScopeOrNull
import org.koin.androidx.scope.LifecycleScopeDelegate
import org.koin.core.Koin
import org.koin.core.component.getScopeId
import org.koin.core.component.getScopeName
import org.koin.core.context.GlobalContext
import org.koin.core.context.KoinContext
import org.koin.core.scope.Scope
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
/** copied from org.koin.androidx.scope.FragmentExt but scope wont link as fragment is not attached */
fun ComponentActivity.activityScopeWithSource() = LifecycleScopeWithSourceDelegate(this)
/** copied from org.koin.androidx.scope.FragmentExt but scope wont link as fragment is not attached */
fun Fragment.fragmentScopeWithSource() = LifecycleScopeDelegate(this) { koin: Koin ->
koin.createScope(getScopeId(), getScopeName(), this)
}
/** links the fragment scope to the activity scope */
fun Fragment.linkScopeToActivity() {
(this as AndroidScopeComponent).scope.linkTo((requireActivity() as AndroidScopeComponent).scope)
}
/** copied from org.koin.android.scope.ServiceExtKt */
fun Service.serviceScopeWithSource() = lazy { getScopeOrNull() ?: createScope(this) }
/** wraps org.koin.androidx.scope.LifecycleScopeDelegate - to add source */
class LifecycleScopeWithSourceDelegate(
val lifecycleOwner: LifecycleOwner,
koinContext: KoinContext = GlobalContext,
createScope: (Koin) -> Scope = { koin: Koin ->
koin.createScope(
lifecycleOwner.getScopeId(),
lifecycleOwner.getScopeName(),
lifecycleOwner
)
},
) : ReadOnlyProperty<LifecycleOwner, Scope> {
private val _lifecycleDelegate = LifecycleScopeDelegate(lifecycleOwner, koinContext, createScope)
override fun getValue(thisRef: LifecycleOwner, property: KProperty<*>): Scope {
return _lifecycleDelegate.getValue(thisRef, property)
}
}

Related

Asynchronous / non main thread initialization in Hilt?

I currently have this code in my app module:
#Singleton
#Provides
fun provideMsalClient(#ApplicationContext context: Context): ISingleAccountPublicClientApplication {
// ISSUE: this does NOT work, as the createSingleAccountPublicClientApplication method
// may not be called from the main thread
return PublicClientApplication.createSingleAccountPublicClientApplication(
context,
R.raw.msal_config
)
}
As I learned from the exception on runtime, PublicClientApplication.createSingleAccountPublicClientApplication (part of the Microsoft Authentication Library, but my question is of general nature) may not be called from the main thread.
So, using Hilt, how can I create an object outside of the main thread / asynchronous?
What about a javax.inject.Provider at the places where you want to inject the class? This should do it without any bigger pain and it won't make it crash when you inject it with Provider - you just have to ensure, that you won't call .get() of the Provider, when you are on the Main Thread.
Here is a minimalistic example.
Classes that I used to mimic the problem:
import android.os.Looper
class PublicClientApplication {
fun createSingleAccountPublicClientApplication(): ISingleAccountPublicClientApplication {
throwOnMainThread("create")
return ISingleAccountPublicClientApplication()
}
private fun throwOnMainThread(methodName: String) {
check(Looper.myLooper() != Looper.getMainLooper()) { "method: $methodName may not be called from main thread." }
}
// just to make it compact here
class ISingleAccountPublicClientApplication {
fun foo() {
println("Bla")
}
}
}
Dagger Binding Code:
#Singleton
#Provides
fun provideISingleAccountPublicClientApplication(): ISingleAccountPublicClientApplication {
return PublicClientApplication().createSingleAccountPublicClientApplication()
}
As Field Injection:
import javax.inject.Inject
import javax.inject.Provider
...
#Inject
lateinit var singleAccountPublicClientApplication: Provider<ISingleAccountPublicClientApplication>
With constructor injection:
import javax.inject.Inject
import javax.inject.Provider
class MyServiceClass #Inject constructor(
#Inject private val accountPublicClientApplication: Provider<ISingleAccountPublicClientApplication>
) {
fun doSomethingAuthenticatedWhichWontBeCalledOnMainThread() {
accountPublicClientApplication.get().foo()
}
}

too many arguments for public constructor `constructerName` defined in com.example.`constructerName` Kotlin [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 1 year ago.
Improve this question
in my MainViewModel class, I have 1 constructed. when I call this in my MainActivity.kt
private val mainViewModel: MainViewModel by lazy {
MainViewModel((application as MainApplication).repository)
}
I am getting an error
Too many arguments for public constructor MainViewModel() defined in com.example.MainViewModel
and if I call something from MainViewModel like this
mainViewModel.subscribedToSleepDataLiveData.observe(this) { newSubscribedToSleepData ->
if (subscribedToSleepData != newSubscribedToSleepData) {
subscribedToSleepData = newSubscribedToSleepData
}
}
I am getting an error
Unresolved reference: subscribedToSleepDataLiveData
Q. How to solve Too many arguments for public constructor MainViewModel() defined in com. example.MainViewModel
MainViewModel.kt
package com.example
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.example.data.SleepRepository
import com.example.data.db.SleepClassifyEventEntity
import com.example.data.db.SleepSegmentEventEntity
import kotlinx.coroutines.launch
class MainViewModel {
class MainViewModel(private val repository: SleepRepository) : ViewModel() {
val subscribedToSleepDataLiveData = repository.subscribedToSleepDataFlow.asLiveData()
fun updateSubscribedToSleepData(subscribed: Boolean) = viewModelScope.launch {
repository.updateSubscribedToSleepData(subscribed)
}
val allSleepSegments: LiveData<List<SleepSegmentEventEntity>> =
repository.allSleepSegmentEvents.asLiveData()
val allSleepClassifyEventEntities: LiveData<List<SleepClassifyEventEntity>> =
repository.allSleepClassifyEvents.asLiveData()
}
class MainViewModelFactory(private val repository: SleepRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return MainViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
}
Note: error is - Too many arguments for public constructor MainViewModel() defined in com.example.MainViewModel
but all files are situated at :- com.example.sleepAndroid
and my package name is also: - com.example
If you find this question unusable, please do not devote this question as I am a super beginner in Kotlin.and I am trying to understand this code.
Q2. what is mean by application as MainApplication
You have a class MainViewModel and that class inside has another MainViewModel. What you are trying to do is accessing the inner class.
MainViewModel.MainViewModel
The instantiation would be
private val mainViewModel: MainViewModel.MainViewModel by lazy {
MainViewModel.MainViewModel((application as MainApplication).repository)
}
Please keep in mind that Kotlin does allow you to have more than one class in a file. For that just delete the outer class MainViewModel { and the closing bracket at the end }, you will end up with one file having 2 classes inside MainViewModel and MainViewModelFactory

Android Implement ViewModel Runtime Error

So I'm trying to understand the ViewModel paradigm by using the code from the Android Developer documentation here
I can't seem to find an example of how to set the value of the users variable. users.value and users.postValue don't seem to work, I get a runtime error.
UPDATE:
I simplified the example in the Android Developer Documentation by using a primitive:
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel() {
private val user: MutableLiveData<String> by lazy {
MutableLiveData<String>().also {
loadUser()
}
}
fun getUser(): LiveData<String> {
return user
}
private fun loadUser() {
user.postValue("My String")
}
}
SOLUTION:
Mark Keen is right. However he inspired me to come up with a different solution than his suggestion in the comments. I needed to understand his motivation for setting the value of the string in the constructor of MutableLiveData instead of using user.value or user.postValue(). The motivation is that I was trying to set the value of user before the variable got initialized. So I thought in this case why not use a lambda expression to pass the value from the scope function directly to loadUsers()?
class MyViewModel : ViewModel() {
private val users: MutableLiveData<String> by lazy {
MutableLiveData<String>().also {
mutableLiveDataString ->
loadUsers(mutableLiveDataString)
}
}
fun getUsers(): LiveData<String> {
return users //My Really Cool String
}
private fun loadUsers(users: MutableLiveData<String>) {
users.postValue("My Really Cool String")
}
}

how to instantiate ViewModel In AndroidX?

I want to initialize ViewModel in Activity using androidx library
I have tried what documentation says but it is not working. the ".of" is not resolved.
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.example.myapplication.databinding.ActivityMainBinding`
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)
binding.setLifecycleOwner(this)
var model = ViewModelProvider.of(this).get(SheduleViewModel::class.java)
}
}
of is not resolved, maybe there are another way to do it in androidx
Updated answer:
Things changed a little bit, as the previously needed dependency - ViewModelProviders - got deprecated (see the old answer for details). You can now use the ViewModelProvider constructor directly.
So, in this case, the answer would be:
private val viewModel = ViewModelProvider(this).get(SheduleViewModel::class.java)
Note that, however, if you include the androidx.activity:activity-ktx:$Version dependency (a few of the commonly used AndroidX dependencies already include it for you), you can make use of property delegation:
private val viewModel: SheduleViewModel by viewModels()
Which internally will use ViewModelProvider and scope your ViewModel to your Activity. It's just a more concise way of writing the same thing. You can do the same for a Fragment by including the androidx.fragment:fragment-ktx:$Version dependency instead (again, commonly already included by other AndroidX dependencies).
Both the ViewModelProvider constructor and by viewModels() also accept a factory as a parameter (useful for injecting your ViewModel):
private val viewModel =
ViewModelProvider(this, viewModelFactory).get(SheduleViewModel::class.java)
and
private val viewModel: SheduleViewModel by viewModels { viewModelFactory }
Use the one that best suits you.
Old answer:
Add the androidx.lifecycle:lifecycle-extensions:$lifecycleExtensionsVersion dependency in order to import ViewModelProviders.
Updating ViewModel to Lifecycle Version 2.2.0 and Above
The ViewModels (VMs) may theoretically be initialized as class level instance variables using the Kotlin extension library import androidx.fragment.app.viewModels method by viewmodels(). By initializing the VM as a class level instance var it can be accessed within the class.
Question: Is there a downside to initializing the VMs as class level instance variables instead of inside onCreate?
When creating the VMs with the extension function inside onCreate the VMs are only scoped within onCreate and extra code is required to reassign the class level instance variables.
See documentation
ViewModel Overview
Lifecycle
Initialize VM as Class Instance Val
class Fragment : Fragment() {
private val viewModel: SomeViewModel by viewModels()
private fun observeViewState() {
viewModel.feedViewState.observe(viewLifecycleOwner) { viewState ->
//viewState used here.
}
}
}
Initialize VM in onCreate and Reassign Class Instance Var
class Fragment : Fragment() {
private lateinit var viewModel: SomeViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel: ContentViewModel by viewModels()
this.viewModel = viewModel
}
private fun observeViewState() {
viewModel.feedViewState.observe(viewLifecycleOwner) { viewState ->
//viewState used here.
}
}
}
Passing Arguments/Parameters
// Override ViewModelProvider.NewInstanceFactory to create the ViewModel (VM).
class SomeViewModelFactory(private val someString: String): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = SomeViewModel(someString) as T
}
class SomeViewModel(private val someString: String) : ViewModel() {
init {
//TODO: Use 'someString' to init process when VM is created. i.e. Get data request.
}
}
class Fragment: Fragment() {
// Create VM in activity/fragment with VM factory.
val someViewModel: SomeViewModel by viewModels { SomeViewModelFactory("someString") }
}
Enabling SavedState with Arguments/Parameters
class SomeViewModelFactory(
private val owner: SavedStateRegistryOwner,
private val someString: String) : AbstractSavedStateViewModelFactory(owner, null) {
override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, state: SavedStateHandle) =
SomeViewModel(state, someString) as T
}
class SomeViewModel(private val state: SavedStateHandle, private val someString: String) : ViewModel() {
val feedPosition = state.get<Int>(FEED_POSITION_KEY).let { position ->
if (position == null) 0 else position
}
init {
//TODO: Use 'someString' to init process when VM is created. i.e. Get data request.
}
fun saveFeedPosition(position: Int) {
state.set(FEED_POSITION_KEY, position)
}
}
class Fragment: Fragment() {
// Create VM in activity/fragment with VM factory.
val someViewModel: SomeViewModel by viewModels { SomeViewModelFactory(this, "someString") }
private var feedPosition: Int = 0
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
someViewModel.saveFeedPosition((contentRecyclerView.layoutManager as LinearLayoutManager)
.findFirstVisibleItemPosition())
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
feedPosition = someViewModel.feedPosition
}
}
For me, the only thing that worked:
implementation 'androidx.fragment:fragment:1.2.4'
PS. This is for someone who is using java and got stuck for a while like I did and this SO answer comes up in google all the time.
Apparently, the API has change as of this date (6 May 2020), I had to do this to get it working.
// 1. Create a ViewModel Class Let's call it AppStateViewModel
// 2. Put below code Inside Activity onCreate like this:
ViewModelProvider.Factory factory = new ViewModelProvider.NewInstanceFactory();
appStateManager = new ViewModelProvider(this, factory).get(AppStateViewModel.class);
ViewModelProviders: This class is deprecated. Use the constructors for ViewModelProvider directly.
Examples in Kotlin
This is how you can use ViewModelProvider directly:
If your view-model is extending AndroidViewModel with just one argument, the app - then you can use the default AndroidViewModelFactory and you don't have to write a new Factory. Example:
// Activity / fragment class
private lateinit var viewModel: MyOwnAndroidViewModel
// onCreate
viewModel = ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory(application)
).get(MyOwnAndroidViewModel::class.java)
If your view-model is only extending the ViewModel without extra arguments then use the NewInstanceFactory().
// Activity / fragment class
private lateinit var viewModel: MyOwnViewModel
// onCreate
viewModel = ViewModelProvider(
this,
ViewModelProvider.NewInstanceFactory()
).get(MyOwnViewModel::class.java)
Adam's answer above covers other variations as well.
Disclaimer: Still learning basic Android development - if there's any problem with the code, let me know in comments.
(How to) Use ViewModel from Android Architecture Component :
Add the Google Maven repository (Optional, just verify that)
Android Studio projects aren't configured to access this repository by default.
To add it to your project, open the build.gradle file for your project (not the ones for your app or module) and add the google() repository as shown below:
allprojects {
repositories {
google()
jcenter()
}
}
Declaring dependencies
Open your app-level build.gradle file,
Go to dependencies{} block
Put implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" for AndroidX version, $lifecycle_version here is latest version defined.
For Pre-AndroidX use implementation "android.arch.lifecycle:viewmodel:1.1.1" (1.1.1 is the last version from this artifact i guess.)
In your activity, use like this syntax
Import this class :
import androidx.lifecycle.ViewModelProviders; for AndroidX
import android.arch.lifecycle.ViewModelProviders; when using Pre-AndroidX
And obtain your ViewModel like following
ViewModelProviders.of(this).get(ProfileObservableViewModel::class.java) // Kotlin syntax
---- or ----
ViewModelProviders.of(this).get(ProfileObservableViewModel.class); // Java syntax
In your app gradle file make sure you have added below dependencies:
For Activity use:
implementation "androidx.activity:activity-ktx:1.4.1"
For Fragment use:
implementation 'androidx.fragment:fragment:1.4.1'
Paste the code below in build.gradle(:app)
implementation 'androidx.fragment:fragment-ktx:1.4.1'
paste the following or similar(relevant to your settings) in app.gradle under dependencies
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
I add the last version of this dependency from
https://developer.android.com/kotlin/ktx/extensions-list
implementation "androidx.activity:activity-ktx:1.4.0"

How can I add unit test for android architecture components life cycle event?

I tried to add a unit test for my function which supports architecture components lifecycle event. To support lifecycle event, I added the #OnLifecycleEvent annotation for my function which I want to do something when that event occurred.
Everything is working as expected but I want to create a unit test for that function to check my function running when the intended event occurred.
public class CarServiceProvider implements LifecycleObserver {
public void bindToLifeCycle(LifecycleOwner lifecycleOwner) {
lifecycleOwner.getLifecycle().addObserver(this);
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onClear() {
Log.i("CarServiceProvider", "onClear called");
}
}
I tried to mock LifecycleOwner and create new LifecycleRegistery to change the state of lifecycle observer but I didn't do.
How can I test my onClear() function called when state changed ?
You should be able to use the LifecycleRegistry
Your test would do something like below:
#Test
public void testSomething() {
LifecycleRegistry lifecycle = new LifecycleRegistry(mock(LifecycleOwner.class));
// Instantiate your class to test
CarServiceProvider carServiceProvider = new CarServiceProvider();
carServiceProvider.bindToLifeCycle(lifecycle);
// Set lifecycle state
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
// Verify that the ON_STOP event was handled, with an assert or similar check
...
}
If you are testing Lifecycle.Event.ON_DESTROY then you probably need to call handleLifecycleEvent(Lifecycle.Event.ON_CREATE) prior to this.
You can test using Unit tests without Roboletric as long as you mock the lifecycle owner properly.
val lifecycleOwner: LifecycleOwner = Mockito.mock(LifecycleOwner::class.java)
val lifecycle = LifecycleRegistry(Mockito.mock(LifecycleOwner::class.java))
lifecycle.markState(Lifecycle.State.RESUMED)
Mockito.`when`(lifecycleOwner.lifecycle).thenReturn(lifecycle)
Use this lifecycleOwner when you observe a variable and you can use Mockito.verify to see if your callback has been called
Despite of I am not a big fan, I would go with Robolectric making use of ActivityController to achieve this.
Given the fact that the "Observables" of the Observer pattern applied in
Android Lifecycle workflow are Activities, Fragments... An application context is a must and we need somehow bring it to our test scenario.
I achieved the expected result by doing this
build.gradle
testCompile "org.robolectric:robolectric:3.5.1"
CarServiceProviderTest.java
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class)
public class CarServiceProviderTest {
#Test
public void shouldFireOnClear(){
//Grab the Activity controller
ActivityController controller = Robolectric.buildActivity(JustTestActivity.class).create().start();
AppCompatActivity activity = (AppCompatActivity) controller.get();
//Instanciate our Observer
CarServiceProvider carServiceProvider = new CarServiceProvider();
carServiceProvider.bindToLifeCycle(activity);
//Fire the expected event
controller.stop();
//Assert
Assert.assertTrue(carServiceProvider.wasFired);
}
}
CarServiceProvider.java
public class CarServiceProvider implements LifecycleObserver {
public boolean wasFired;
public void bindToLifeCycle(LifecycleOwner lifecycleOwner) {
lifecycleOwner.getLifecycle().addObserver(this);
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onClear() {
wasFired = true;
}
}
You can use the TestLifecycleOwner from the
androidx.lifecycle:lifecycle-runtime-testing
dependency
https://developer.android.com/reference/kotlin/androidx/lifecycle/testing/TestLifecycleOwner
Expanding on Raz's answer, in Kotlin you can make an extension function to make this more reusable.
fun LifecycleObserver.testLifeCycleEvent(lifecycleEvent: Lifecycle.Event) {
val mockLifeCycleOwner: LifecycleOwner = mockk()
val lifecycleRegistry = LifecycleRegistry(mockLifeCycleOwner)
lifecycleRegistry.addObserver(this)
lifecycleRegistry.handleLifecycleEvent(lifecycleEvent)
}
In the original questioner's case, they have a fun bindToLifecycle(). If you are doing something like that, you can just make an extension function for that as well so it applies to all LifecycleObserver types:
fun LifecycleObserver.bindToLifeCycle(lifecycle: Lifecycle) {
lifecycle.addObserver(this);
}
and then modify testLifeCycleEvent() like this:
fun LifecycleObserver.testLifeCycleEvent(lifecycleEvent: Lifecycle.Event) {
val mockLifeCycleOwner: LifecycleOwner = mockk()
val lifecycleRegistry = LifecycleRegistry(mockLifeCycleOwner)
this.bindLifecycle(lifecycleRegistry)
lifecycleRegistry.handleLifecycleEvent(lifecycleEvent)
}
If you want to test it with a real Unit test (not AndroidTest), your best bet is to use Robolectric, it mocks the Android framework and Robolectric 4.0 is coming out. However you're trying to test the actual interaction with the Android Framework, so that's a task better suited for a true integration testing suite and an AndroidTest. You can unit test it but the most sure way to test it would be to actually invoke the lifecycle event on a device (What Espresso would do) and verify it's called.
There is a sample for Powermock v3.8 and RESUME event. I use this event to show how to verify livedata value
#build.gradle
dependencies {
...
testImplementation 'org.robolectric:robolectric:3.8'
testImplementation "org.powermock:powermock-module-junit4:2.0.2"
testImplementation "org.powermock:powermock-module-junit4-rule:2.0.2"
testImplementation "org.powermock:powermock-api-mockito2:2.0.2"
testImplementation "org.powermock:powermock-classloading-xstream:1.6.4"
...
}
#CustomViewModel.kt
class CustomViewModel() : ViewModel(), LifecycleObserver {
private val _outputData = MutableLiveData<Boolean>()
val outputData: LiveData<Boolean> = _outputData
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
//TODO action
outputData.value = true
}
}
#CustomViewModelTest.kt
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.modules.junit4.PowerMockRunnerDelegate
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
#RunWith(PowerMockRunner::class)
#PowerMockRunnerDelegate(RobolectricTestRunner::class)
#PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*")
#Config(manifest = Config.NONE)
class CustomViewModelTest {
#Mock
lateinit var observer: Observer<in Boolean>
#Before
fun beforeTest() {
MockitoAnnotations.initMocks(this)
}
#Test
#Config(sdk = [Build.VERSION_CODES.O])
fun `do an action on resume event`() {
val vm = spy(CustomViewModel())
vm.outputData.observeForever(observer)
vm.testLifecycleEvent(Lifecycle.Event.ON_RESUME)
verify(vm).onResume()
verify(observer).onChange(true)
}
}
fun LifecycleObserver.testLifecycleEvent(lifecycleEvent: Lifecycle.Event) {
val lifecycleOwner = Mockito.mock(LifecycleOwner::class.java)
val lifecycleRegistry = LifecycleRegistry(lifecycleOwner)
this.bindToLifecycle(lifecycleRegistry)
lifecycleRegistry.handleLifecycleEvent(lifecycleEvent)
}
fun LifecycleObserver.bindToLifecycle(lifecycle: Lifecycle) {
lifecycle.addObserver(this)
}

Categories

Resources