Is there a way to mock ViewModel that's built is inside of a fragment? I'm trying to run some tests on a fragment, one of the fragment functions interacts with the ViewModel, I would like to run the test function and provided a mocked result for the ViewModel. Is this even possilbe?
MyFragment
class MyFragment : Fragment() {
#Inject
lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
(requireActivity().application as MyApplication).appComponent.inject(this)
super.onCreate(savedInstanceState)
}
}
Test
#RunWith(RoboeltricTestRunner::) {
#Before
fun setup() {
FragmentScenario.Companion.launchIncontainer(MyFragment::class.java)
}
}
Yeah, just mark your ViewModel open and then you can create a mock implementation on top of it.
open class MyViewModel: ViewModel() {
fun myMethodINeedToMock() {
}
}
class MockMyViewModel: MyViewModel() {
override fun myMethodINeedToMock() {
// don't call super.myMethodINeedToMock()
}
}
So, register your MockMyViewModel to the DI framework when testing.
I thought I would post this for anyone else struggling to find a solution. You'll want to use a Fragment Factory, that has a dependency on the ViewModel. Injecting the ViewModel into the fragments constructor allows the ViewModel to easliy be mocked. There are a few steps that need to be completed for a FragmentFactory but it's not that complicated once you do a couple of them.
Fragment Add the ViewModel into the constructor.
class MyFragment(private val viewModel: ViewModel) : Fragment {
...
}
FragmentFactory, allows fragments to have dependencies in the constructor.
class MyFragmentFactory(private val viewModel: MyViewModel) : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
return when(className) {
MyFirstFragment::class.java.name -> {
MyFragment(viewModel)
}
// You could use this factory for multiple Fragments.
MySecondFragment::class.java.name -> {
MySecondFragment(viewModel)
}
// You also don't have to pass the dependency
MyThirdFragment::class.java.name -> {
MyThirdFragment()
}
else -> super.instantiate(classLoader, className)
}
}
}
Main Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Create your ViewModel
val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
// create the FragmentFactory and the viewModel dependency.
supportFragmentManager.fragmentFactory = MainFragmentFactory(viewModel)
// FragmentFactory needs to be created before super in an activity.
super.onCreate(savedInstanceState)
}
}
Test
#RunWith(RobolectricTestRunner::class)
class MyFragmentUnitTest {
#Before
fun setup() {
val viewModel: MainViewModel = mock(MyViewModel::class.java)
...
}
}
Related
I've created a structure in app with BaseActivity and BaseViewModel. All other activities/viewModels must be extend with this base classes. I made that cause i need to call some methods in any activity (like showInfo() method).
When i update LiveData in BaseViewModel and observe it in BaseActivity all works well. But when i update that LiveData in child ViewModel (e.g. UsersViewModel) only with BaseActivity observing its won't work.
What should i do when i want to call some base method in any activity through ViewModel?
open class BaseActivity : AppCompatActivity() {
//inject viewModel with Koin
private val baseViewModel: BaseViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
baseViewModel.actionShowInfo.observe(this, Observer {
showInfo(it)
}
}
protected fun showInfo(message: String) {
AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton(R.string.ok, null)
.show()
}
}
open class BaseViewModel : ViewModel() {
private val actionShowInfo = MutableLiveData<String>()
init {
actionShowInfo.postValue("some base info") //showInfo() in BaseActivity will be called
}
}
class UsersActivity : BaseActivity() {
private val usersViewModel: UsersViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(
}
}
class UsersViewModel: BaseViewModel {
init {
//showInfo() in BaseActivity will not be called
actionShowInfo.postValue("some info")
}
}
Just by extend the UserViewModel your BaseViewModel, doesn't mean it's sharing the same instance. Based on your requirement, I think you need a ViewModel that can share it's instance to several activity, so that when you update the ViewModel on Activity A, you can observe the change on Activiy B, and so on.
This is where SharedViewModel come to rescue. You need to implement a sharedViewModel to all your activity.
private val baseViewModel: BaseViewModel by sharedViewModel()
Reference: https://doc.insert-koin.io/#/koin-android/viewmodel?id=shared-viewmodel
WHY NOT HELP ME ANY PEOPLE???
In my application i used Kotin and Kodein for Dependency injection.
I write below codes, but when run application show me Force close error!
In my application i used Kotin and Kodein for Dependency injection.
I write below codes, but when run application show me Force close error!
Application class codes:
class QuoteDiApp : Application(), KodeinAware {
override val kodein = Kodein.lazy {
bind<Database>() with singleton { DatabaseFakeImpl() }
bind<QuoteDao>() with singleton { instance<Database>().quoteDao }
bind<QuoteRepository>() with singleton { QuoteRepositoryImpl(instance()) }
bind() from provider { QuotesViewModelFactory(instance()) }
}
}
Activity class codes:
class QuoteDiActivity : AppCompatActivity(), KodeinAware {
override val kodein by closestKodein()
private val viewModelFactory: QuoteViewModelFactory by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_quote_di)
initializeUi()
}
private fun initializeUi() {
// Use ViewModelProviders class to create / get already created QuotesViewModel
// for this view (activity)
val viewModel = ViewModelProviders.of(this, viewModelFactory).get(QuoteViewModel::class.java)
// Observing LiveData from the QuotesViewModel which in turn observes
// LiveData from the repository, which observes LiveData from the DAO ☺
viewModel.getQuotes().observe(this, Observer { quotes ->
val stringBuilder = StringBuilder()
quotes.forEach { quote ->
stringBuilder.append("\n$quote")
}
textView_quotes.text = stringBuilder.toString()
})
// When button is clicked, instantiate a Quote and add it to DB through the ViewModel
button_add_quote.setOnClickListener {
val quote = Quote(editText_quote.text.toString(), editText_author.text.toString())
viewModel.addQuote(quote)
editText_quote.setText("")
editText_author.setText("")
}
}
}
I initialize application class into Manifest
Error message :
Caused by: org.kodein.di.Kodein$NotFoundException: No binding found for bind<QuoteViewModelFactory>() with ? { ? }
Registered in this Kodein container:
bind<QuoteDao>() with singleton { QuoteDao }
bind<QuotesViewModelFactory>() with provider { QuotesViewModelFactory }
bind<Database>() with singleton { DatabaseFakeImpl }
bind<QuoteRepository>() with singleton { QuoteRepositoryImpl }
at org.kodein.di.internal.KodeinContainerImpl.factory(KodeinContainerImpl.kt:174)
at org.kodein.di.KodeinContainer$DefaultImpls.factory$default(KodeinContainer.kt:33)
at org.kodein.di.KodeinContainer$DefaultImpls.provider(KodeinContainer.kt:80)
at org.kodein.di.internal.KodeinContainerImpl.provider(KodeinContainerImpl.kt:7)
at org.kodein.di.KodeinContainer$DefaultImpls.provider$default(KodeinContainer.kt:79)
at org.kodein.di.KodeinAwareKt$Instance$1.invoke(KodeinAware.kt:152)
at org.kodein.di.KodeinProperty$provideDelegate$1.invoke(properties.kt:39)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
at nouri.mohammad.mvvm_kodeindi.with_di.ui.quotes.QuoteDiActivity.getViewModelFactory(QuoteDiActivity.kt)
at nouri.mohammad.mvvm_kodeindi.with_di.ui.quotes.QuoteDiActivity.initializeUi(QuoteDiActivity.kt:30)
at nouri.mohammad.mvvm_kodeindi.with_di.ui.quotes.QuoteDiActivity.onCreate(QuoteDiActivity.kt:24)
at android.app.Activity.performCreate(Activity.java:5231)
In your application class you use QuotesViewModelFactory class, but into you activity class you use QuoteViewModelFactory class.
This two classes is not match!
Please check your classes and match this two classes !
I'm pretty new in Android development and currently, I'm testing a basic activity with Roboelectric and Koin.
Code:
class SplashActivity : AppCompatActivity() {
private val viewModel: LoginViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
Stetho.initializeWithDefaults(this)
val user = viewModel.getPersistedUser()
if (user != null) {
viewModel.setUser(user)
startActivity(HomeActivity.getStartIntent(this))
} else {
startActivity(LoginActivity.getStartIntent(this))
}
}
}
val appModule = module(override = true) {
...
viewModel<LoginViewModel>()
}
Now all I want to do in the test is to inject a mocked version of the viewModel to simulate the response of the method getPersistedUser.
How can I do that with Roboelectric and Koin?
First, if you want to write UI test for SplashActivity. Better to use Expresso testing framework (https://developer.android.com/training/testing/espresso)
Second, if you want to mock your viewmodel with Koin in your test, you can load your Koin modules then declare your viewmodel mock, code will be similar like this
class SplashActivityTest : AutoCloseKoinTest() {
private val viewModel: LoginViewModel by inject()
#Before
fun before() {
koinApplication {
loadModules(appModule)
}
declareMock<LoginViewModel> {
given(getPersistedUser()).willReturn { User(username, password) }
}
}
#Test
fun loadCurrentUserWhenActivityInit() {
// verify your test here
}
}
More details here https://start.insert-koin.io/#/getting-started/testing
We have been discussing about this but we don't know the reason of creating a viewmodel factory to create a viewmodel instead of instantiate the viewmodel directly. What is the gain of creating a factory that just creates the viewmodel?
I just put a simple example of how I did it without Factory
here is the kodein module:
val heroesRepositoryModel = Kodein {
bind<HeroesRepository>() with singleton {
HeroesRepository()
}
bind<ApiDataSource>() with singleton {
DataModule.create()
}
bind<MainViewModel>() with provider {
MainViewModel()
}
}
The piece of the Activity where I instantiate the viewmodel without using the factory
class MainActivity : AppCompatActivity() {
private lateinit var heroesAdapter: HeroAdapter
private lateinit var viewModel: MainViewModel
private val heroesList = mutableListOf<Heroes.MapHero>()
private var page = 0
private var progressBarUpdated = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this)
.get(MainViewModel::class.java)
initAdapter()
initObserver()
findHeroes()
}
The ViewModel where I instantiate the usecase directly without having it in the constructor
class MainViewModel : ViewModel(), CoroutineScope {
private val heroesRepository: HeroesRepository = heroesRepositoryModel.instance()
val data = MutableLiveData<List<Heroes.MapHero>>()
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = uiContext + job
fun getHeroesFromRepository(page: Int) {
launch {
try {
val response = heroesRepository.getHeroes(page).await()
data.value = response.data.results.map { it.convertToMapHero() }
} catch (e: HttpException) {
data.value = null
} catch (e: Throwable) {
data.value = null
}
}
}
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
So here a example using factory
class ListFragment : Fragment(), KodeinAware, ContactsAdapter.OnContactListener {
override val kodein by closestKodein()
private lateinit var adapterContacts: ContactsAdapter
private val mainViewModelFactory: MainViewModelFactory by instance()
private val mainViewModel: MainViewModel by lazy {
activity?.run {
ViewModelProviders.of(this, mainViewModelFactory)
.get(MainViewModel::class.java)
} ?: throw Exception("Invalid Activity")
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_list, container, false)
}
The viewmodelfactory:
class MainViewModelFactory (private val getContacts: GetContacts) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(getContacts) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
And the viewmodel:
class MainViewModel(private val getContacts: GetContacts) : BaseViewModel() {
lateinit var gamesList: LiveData<PagedList<Contact>>
var contactsSelectedData: MutableLiveData<List<Contact>> = MutableLiveData()
var contactsSelected: ArrayList<Contact> = ArrayList()
private val pagedListConfig by lazy {
PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(PAGES_CONTACTS_SIZE)
.setPageSize(PAGES_CONTACTS_SIZE)
.setPrefetchDistance(PAGES_CONTACTS_SIZE*2)
.build()
}
Here is the complete first example:
https://github.com/ibanarriolaIT/Marvel/tree/mvvm
And the complete second example:
https://github.com/AdrianMeizoso/Payment-App
We can not create ViewModel on our own. We need ViewModelProviders utility provided by Android to create ViewModels.
But ViewModelProviders can only instantiate ViewModels with no arg constructor.
So if I have a ViewModel with multiple arguments, then I need to use a Factory that I can pass to ViewModelProviders to use when an instance of MyViewModel is required.
For example -
public class MyViewModel extends ViewModel {
private final MyRepo myrepo;
public MyViewModel(MyRepo myrepo) {
this.myrepo = myrepo;
}
}
To instantiate this ViewModel, I need to have a factory which ViewModelProviders can use to create its instance.
ViewModelProviders Utility can not create instance of a ViewModel with argument constructor because it does not know how and what objects to pass in the constructor.
In short,
if we need to pass some input data to the constructor of the viewModel , we need to create a factory class for viewModel.
Like example :-
class MyViewModelFactory constructor(private val repository: DataRepository): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(MyViewModel::class.java!!)) {
MyViewModel(this.repository) as T
} else {
throw IllegalArgumentException("ViewModel Not Found")
}
}
}
Reason
We cannot directly create the object of the ViewModel as it would not be aware of the lifecyclerOwner. So we use :-
ViewModelProviders.of(this, MyViewModelFactory(repository)).get(MyViewModel::class.java)
We have been discussing about this but we don't know the reason of creating a viewmodel factory to create a viewmodel instead of instantiate the viewmodel directly. What is the gain of creating a factory that just creates the viewmodel?
Because Android will only give you a new instance if it's not yet created for that specific given ViewModelStoreOwner.
Let's also not forget that ViewModels are kept alive across configuration changes, so if you rotate the phone, you're not supposed to create a new ViewModel.
If you are going back to a previous Activity and you re-open this Activity, then the previous ViewModel should receive onCleared() and the new Activity should have a new ViewModel.
Unless you're doing that yourself, you should probably just trust the ViewModelProviders.Factory to do its job.
(And you need the factory because you typically don't just have a no-arg constructor, your ViewModel has constructor arguments, and the ViewModelProvider must know how to fill out the constructor arguments when you're using a non-default constructor).
When we are simply using ViewModel, we cannot pass arguments to that ViewModel
class GameViewModel() : ViewModel() {
init {
Log.d(TAG, "GameViewModel created")
}
}
However, in some cases, we need to pass our own arguments to ViewModel. This can be done using ViewModelFactory.
class ScoreViewModel(finalScore: Int) : ViewModel() {
val score = finalScore
init {
Log.d(TAG, "Final score: $finalScore")
}
}
And to instantiate this ViewModel, we need a ViewModelProvider.Factory as simple ViewModel cannot instantiate it.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
When it comes to instantiating object of this ViewModel i.e with ViewModelProvider, we pass ViewModelFactory as an argument which contains information about our custom arguments which we want to pass. It goes like:
viewModelFactory = ScoreViewModelFactory(score)
viewModel = ViewModelProvider(this,viewModelFactory).get(ScoreViewModel::class.java)
That is why factory methods are there.
I am a newbie to android and trying to use Dagger2. I spend whole night and still dont know why my dagger does not provide presenter. Here are my code (I use Kotlin)
AppComponent
#Singleton
#Component(modules = arrayOf(PresenterModule::class))
interface AppComponent {
fun inject(target: SplashActivity)
}
PresenterModule
#Module
class PresenterModule {
#Provides
#Singleton
fun provideSplashPresenter(): SplashPresenter {
return SplashPresenter()
}
}
App
class App: Application() {
companion object {
lateinit var appComponent: AppComponent
}
override fun onCreate() {
super.onCreate()
appComponent = initDagger()
}
private fun initDagger(): AppComponent {
return DaggerAppComponent.create()
}
}
This is the presenter
class SplashPresenter: BasePresenterImpl<SplashContract.View>(), SplashContract.Presenter {
override fun performToast(mess: String) {
logi("abc", "performToast")
logi("abc", "mess: " + mess)
mView?.showLoading()
if (mess.isNullOrBlank()) {
mView?.showTosat("this is empty mess") ?: logi("abc", "null")
} else {
mView?.showTosat(mess) ?: logi("abc", "null")
}
mView?.hideLoading()
}
}
And finally, this is my SplashActivity
class SplashActivity : BaseActivity(), SplashContract.View {
#Inject
lateinit var presenter: SplashPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
presenter.attachView(this)
//TODO: check log in
//TODO: If logged in => start main screen
//TODO: If not logged in => load login activity
button.setOnClickListener{
presenter.performToast(editText.text.toString())
logi("abc", "perform clicked")
}
}
}
When I run these code, I got this error
Lateinit property presenter has not been initialized, which means that "Inject" does not work
Since you're not using constructor injection here (which you can't, because you don't 'own' the activity's constructor) Dagger does not 'know' that it has to inject something into your Activity.
You have to manually inject like this:
(applicationContext as App).appComponent.inject(this)
in your SplashActivity's onCreate() method (before using the presenter, of course).
Second, your presenter needs a constructor that tells Dagger how to construct/'build' the presenter, which means a constructor annotated with the #Inject annotation, so:
class SplashPresenter #Inject constructor(): BasePresenterImpl<SplashContract.View>(), SplashContract.Presenter
You forgot to inject the SplashActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
App.appComponent.inject(this)
setContentView(R.layout.activity_splash)
...