I have added the following lines to the gradle file
plugins {
...
id 'kotlin-kapt'
}
dependencies {
...
implementation 'com.google.dagger:dagger:2.28.3'
annotationProcessor 'com.google.dagger:dagger-compiler:2.28.3'
implementation 'com.google.dagger:dagger-android-support:2.28.3' // if you use the support libraries
}
I create a factory ViewModel
class MainViewModelFactory #Inject constructor(private val mainViewModel: MainViewModel) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return mainViewModel as T
}
throw IllegalArgumentException("Unknown class name")
}
}
Then I call (naturally with the help of retrofit services)
private val viewModel: MainViewModel by viewModels {
DaggerAppComponent.create().mainViewModelFactory()
}
According to several sources of information, after building the project, I expect an error that the DaggerAppComponent class was not created, after which, I also expect that it is created automatically and I can add it using import. However, this does not happen. What can be wrong?
According to several sources of information, after building the project, I expect an error that the DaggerAppComponent class was not created, after which, I also expect that it is created automatically and I can add it using import. However, this does not happen. What can be wrong?
So I want to test my jetpack compose project. It's easy enough running an instrument test following [these instructions]1 on android dev site, but when you add #HiltViewModel injection into the combination things get complicated.
I'm trying to test a pretty simple compose screen with a ViewModel that has an #Inject constructor.
The screen itself looks like this:
#Composable
fun LandingScreen() {
val loginViewModel: LoginViewModel = viewModel()
MyTheme {
Surface(color = MaterialTheme.colors.background) {
val user by loginViewModel.user.observeAsState()
if (user != null) {
MainScreen()
} else {
LoginScreen(loginViewModel)
}
}
}
}
and this is the view model:
#HiltViewModel
class LoginViewModel #Inject constructor(private val userService: UserService) : ViewModel() {
val user = userService.loggedInUser.asLiveData()
}
User service is of course backed by a room database and the loggedInUser property returns a Flow.
Things work as expected on standard run but when trying to run it in an instrument test it can't inject the view model.
#HiltAndroidTest
class LandingScreenTest {
#get:Rule
var hiltRule = HiltAndroidRule(this)
#get:Rule
val composeTestRule = createComposeRule()
#Inject
lateinit var loginViewModel: LoginViewModel
#Before
fun init() {
hiltRule.inject()
}
#Test
fun MyTest() {
composeTestRule.setContent {
MyTheme {
LandingScreen()
}
}
composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()
}
}
Injection of an #HiltViewModel class is prohibited since it does not
create a ViewModel instance correctly. Access the ViewModel via the
Android APIs (e.g. ViewModelProvider) instead. Injected ViewModel:
com.example.viewmodels.LoginViewModel
How do you make that work with the ViewModelProvider instead of the #HiltViewModel?
Hilt needs an entry point to inject fields. In this case that would probably be an Activity annotated with #AndroidEntryPoint. You can use your MainActivity for that, but that would mean that you would then have to add code to every test to navigate to the desired screen which could be tedious depending on the size of your app, and is not feasible if your project is multimodule and your current Test file does not have access to MainActivity. Instead, you could create a separate dummy Activity whose sole purpose is to host your composable (in this case LoginScreen) and annotate it with #AndroidEntryPoint. Make sure to put it into a debug directory so it's not shipped with the project. Then you can use createAndroidComposeRule<Activity>() to reference that composable. You dont need to inject the ViewModel directly so get rid of that line too.
In the end your Test File should look like this:
#HiltAndroidTest
class LandingScreenTest {
#get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)
#get:Rule(order = 1)
val composeRule = createAndroidComposeRule<LoginTestActivity>()
#Before
fun init() {
hiltRule.inject()
}
#Test
fun MyTest() {
composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()
}
}
And your your dummy activity can look like this:
#AndroidEntryPoint
class LoginTestActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LoginScreen()
}
}
}
And the debug directory would look like this:debug directory with dummy activity
Yes the debug directory has its own manifest and that is where you should add the dummy activity. set exported to false.
Try to do something like this:
#HiltAndroidTest
class LandingScreenTest {
#get:Rule
var hiltRule = HiltAndroidRule(this)
#get:Rule
val composeTestRule = createComposeRule()
// Remove this line #Inject
lateinit var loginViewModel: LoginViewModel
#Before
fun init() {
hiltRule.inject()
}
#Test
fun MyTest() {
composeTestRule.setContent {
loginViewModel= hiltViewModel() // Add this line
MyTheme {
LandingScreen()
}
}
composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()
}
}
You must annotate any UI test that uses Hilt with #HiltAndroidTest. This annotation is responsible for generating the Hilt components for each test.
https://developer.android.com/training/dependency-injection/hilt-testing
I use hilt for dependency injection.
I use this articles for implements test.
https://developer.android.com/training/dependency-injection/hilt-testing
https://dagger.dev/hilt/testing.html
now wanna write test for my fragments.
I have a problem with add library.
this is my code
this class is my runner and use in gradle
class CustomTestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, MyCustom_Application::class.java.name, context)
}
}
this is interface for hilt to generate an application class that i use in runner class
#CustomTestApplication(AndroidApplication::class)
interface MyCustom
this is my test class
#HiltAndroidTest
class SettingsActivityTest {
#get:Rule
var hiltRule = HiltAndroidRule(this)
// UI tests here.
#Test
fun test(){
// val activityScenario = launchActivity<MainActivity>()
}
}
my libraries
// For instrumented tests.
androidTestImplementation("com.google.dagger:hilt-android-testing:2.28-alpha")
// ...with Kotlin.
kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.34.1-beta")
androidTestImplementation("junit:junit:4.12")
androidTestImplementation ("androidx.test.ext:junit:1.1.2")
androidTestImplementation ("androidx.test.espresso:espresso-core:3.3.0")
but my problem:
when i add this line
kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.34.1-beta")
gradle show this error message:
error: SettingsActivityTest_TestComponentDataSupplier is not abstract and does not override abstract method get() in TestComponentDataSupplier
public final class SettingsActivityTest_TestComponentDataSupplier extends TestComponentDataSupplier {
^
and when i use this line
kaptTest("com.google.dagger:hilt-android-compiler:2.34.1-beta")
android studio cant generate MyCustom_Application
I have encountered this problem before. This problem occurs due to incompatibility of versions.
use :
androidTestImplementation("com.google.dagger:hilt-android-testing:2.34.1-beta")
kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.34.1-beta")
implementation("com.google.dagger:hilt-android:2.34.1-beta") //hilt dependency
instead of this :
androidTestImplementation("com.google.dagger:hilt-android-testing:2.28-alpha")
kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.34.1-beta")
I'm trying to launch tests with Koin DI help like in the example https://insert-koin.io/docs/2.0/documentation/koin-core/index.html#_making_your_test_a_koincomponent_with_kointest or https://insert-koin.io/docs/2.0/getting-started/junit-test/
but every time getting NoSuchMethodError. What I am doing wrong?
First I was using already created modules from main package, but there was this error. Then I created modules in test package, but error is still the same.
My code
class ComponentA
class ComponentB(val a: ComponentA)
class SignInTest : KoinTest {
val componentB : ComponentB by inject()
#Before
fun before() {
startKoin { modules(
module {
single { ComponentA() }
single { ComponentB(get()) }
}) }
}
#Test
fun test_test() {
val componentA = get<ComponentA>()
assertNotNull(componentA)
assertEquals(componentA, componentB.a)
}
#After
fun after() {
stopKoin()
}
java.lang.NoSuchMethodError: org.koin.core.definition.BeanDefinition.(Lorg/koin/core/qualifier/Qualifier;Lorg/koin/core/qualifier/Qualifier;Lkotlin/reflect/KClass;)V
at net.app.at.features.signin.SignInTest$before$1$1.invoke(SignInTest.kt:79)
at net.app.at.features.signin.SignInTest$before$1$1.invoke(SignInTest.kt:26)
at org.koin.dsl.ModuleKt.module(Module.kt:31)
at org.koin.dsl.ModuleKt.module$default(Module.kt:29)
at net.app.at.features.signin.SignInTest$before$1.invoke(SignInTest.kt:36)
at net.app.at.features.signin.SignInTest$before$1.invoke(SignInTest.kt:26)
at org.koin.core.context.GlobalContextKt.startKoin(GlobalContext.kt:72)
at net.app.at.features.signin.SignInTest.before(SignInTest.kt:35)
Please check versions of Koin libraries you are using.
I had same problem. It turns out that I had in my build.gradle:
implementation "org.koin:koin-android:2.0.0-beta-1"
and a few lines below:
testImplementation "org.koin:koin-test:2.0.0"
When I set version 2.0.0 in both places - it worked.
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"