I'm just starting with Jetpack Compose and Hilt. But I'm running into issues when I inject into a ViewModel.
The error that I get:
java.lang.RuntimeException: Cannot create an instance of class com.example.chaes.login.viewModel.SignUpViewModel
Caused by: java.lang.InstantiationException: java.lang.Class<com.example.chaes.login.viewModel.SignUpViewModel> has no zero argument constructor
I can inject all fine in the Activity but not in the ViewModel. I've tried all solutions I could find.
My gradle files:
Project root level:
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10"
classpath 'com.google.gms:google-services:4.3.8'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.37'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
Module level:
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
kapt{
correctErrorTypes true
}
dependencies {
...
// Hilt
implementation "com.google.dagger:hilt-android:2.37"
kapt "com.google.dagger:hilt-android-compiler:2.37"
...
}
My application file
#HiltAndroidApp
class BaseApplication: Application() {
}
My Module File:
#Module
#InstallIn(SingletonComponent::class)
object AuthRepoModule {
#Singleton
#Provides
fun provideAuthRepo(): FirebaseAuthRepo{
return FirebaseAuthRepo()
}
#Singleton
#Provides
fun provideRandomString(): String{
return "gejifeg"
}
}
The project is single activity with composable screens so the MainActivity:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() { ... }
ViewModel:
#HiltViewModel
class SignUpViewModel #Inject constructor(
firebaseAuthRepo: FirebaseAuthRepo,
) : ViewModel() { ... }
Things I've tried:
Changing to ViewModelComponent instead of Singleton
changing to kapt "com.google.dagger:hilt-compiler:2.37" instead of kapt "com.google.dagger:hilt-android-compiler:2.37"
deleting the build folder and rebuilding the project
invalidating cache and restarting
Edit: Solution Found!
As mentioned by #sitatech in the comments, one needs to use hiltViewModel() instead of viewModel() to provide the viewModel to the composable.
In app module dependency add
...
// Hilt
implementation "com.google.dagger:hilt-android:2.37"
kapt "com.google.dagger:hilt-android-compiler:2.37"
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
kapt "androidx.hilt:hilt-compiler:1.0.0-alpha03"
...
Other portions seem ok to me.
As mentioned by #sitatech in the comments, I needed to use hiltViewModel() instead of viewModel() in the composable.
Related
Here is my AllFilesListViewModel class.
class AllFilesListViewModel #ViewModelInject constructor(
private val pdfItemRepository: PdfItemRepository):ViewModel() {
}
Here is PdfItemRepository class.
#Singleton
class PdfItemRepository #Inject constructor(private val pdfItemDao: PdfItemDao){
}
For pdfItemDao. I created a module named DatabaseModule. Below is the code -
#Module
#InstallIn(ApplicationComponent::class)
object DatabaseModule {
#Provides
fun provideDatabase(#ApplicationContext context: Context):AppDatabase{
return AppDatabase.getDataBase(context)
}
#Provides
fun providePdfItemDao(database:AppDatabase):PdfItemDao{
return database.pdfItemDao()
}
}
Here is the fragment class AllFilesFragment.kt where I am using viewModel.
#AndroidEntryPoint
class AllFilesFragment:Fragment(){
private lateinit var binding:AllFilesFragmentBinding
private val viewModel by viewModels<AllFilesListViewModel>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = AllFilesFragmentBinding.inflate(inflater,container,false)
context?: return binding.root
initThings()
subscribeUi()
return binding.root
}
}
Here is logcat file.
06-19 19:22:20.203 23753-23753/com.emptysheet.pdfreader_autoscroll E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.emptysheet.pdfreader_autoscroll, PID: 23753
java.lang.RuntimeException: Cannot create an instance of class com.emptysheet.pdfreader_autoscroll.homeScreen.viewModel.AllFilesListViewModel
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:221)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:278)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:106)
at androidx.hilt.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:69)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:69)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:54)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:41)
at com.emptysheet.pdfreader_autoscroll.homeScreen.AllFilesFragment.getViewModel(AllFilesFragment.kt)
at com.emptysheet.pdfreader_autoscroll.homeScreen.AllFilesFragment.subscribeUi(AllFilesFragment.kt:72)
at com.emptysheet.pdfreader_autoscroll.homeScreen.AllFilesFragment.onCreateView(AllFilesFragment.kt:64)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2698)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:320)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1187)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1434)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:442)
at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:2169)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1992)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1947)
at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1818)
at androidx.fragment.app.BackStackRecord.commitNow(BackStackRecord.java:297)
at androidx.viewpager2.adapter.FragmentStateAdapter.placeFragmentInViewHolder(FragmentStateAdapter.java:341)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:276)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:67)
at androidx.recyclerview.widget.RecyclerView.dispatchChildAttached(RecyclerView.java:7556)
at androidx.recyclerview.widget.RecyclerView$5.addView(RecyclerView.java:860)
at androidx.recyclerview.widget.ChildHelper.addView(ChildHelper.java:107)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:8601)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8559)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8547)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1641)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
at android.view.View.layout(View.java:15689)
at android.view.ViewGroup.layout(ViewGroup.java:5048)
at androidx.viewpager2.widget.ViewPager2.onLayout(ViewPager2.java:527)
at android.view.View.layout(View.java:15689)
at android.view.ViewGroup.layout(ViewGroup.java:5048)
at com.google.android.material.appbar.HeaderScrollingViewBehavior.layoutChild(HeaderScrollingViewBehavior.java:148)
at com.google.android.material.appbar.V
This happens to me when using Hilt , and that was because I forgot to add the #AndroidEntryPoint annotation on top of the fragment class.
Both the fragment and the host activity should be annotated with this annotation.
This got solved after I used kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01' in app's build.gradle. I had already added kapt "com.google.dagger:hilt-android-compiler:2.28-alpha". I still didn't understand the difference between two BTW. If anyone knows. Please explain it to me.
This is caused by a version mismatch between AndroidX Lifecycle, AndroidX Core, AndroidX Activity and AndroidX Fragment.
Hilt only works if getDefaultViewModelProviderFactory can be overridden.
This is only true if that method actually exists, which it doesn't if your dependencies are out of date. Namely, your androidx.fragment is lower than 1.2.0, and your androidx.activity is lower than 1.1.0.
Use this and it'll work:
implementation "androidx.appcompat:appcompat:1.4.1"
implementation "androidx.core:core-ktx:1.7.0"
implementation "androidx.activity:activity-ktx:1.4.0"
implementation "androidx.fragment:fragment-ktx:1.4.1"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.4.1"
But currently, this is what makes it work for me:
buildscript {
ext {
dagger_version = '2.41'
}
dependencies {
classpath "com.google.dagger:hilt-android-gradle-plugin:$dagger_version"
}
and
apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'kotlin-kapt'
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
implementation "com.google.dagger:hilt-android:$dagger_version"
kapt "com.google.dagger:hilt-android-compiler:$dagger_version"
kaptTest "com.google.dagger:hilt-android-compiler:$dagger_version"
kaptAndroidTest "com.google.dagger:hilt-android-compiler:$dagger_version"
kapt 'androidx.hilt:hilt-compiler:1.0.0'
#ViewModelInject is deprecated in the newer hilt version
Reference
Use HiltViewModel
#HiltViewModel
class AllFilesListViewModel #Inject constructor(
val pdfItemRepository: PdfItemRepository)
) : ViewModel() {
}
When I use Jetpack Compose, Hilt and Compose Navigation, my approach is to get all the dependencies in the docs, and make sure that all of their versions are up-to-date. The key is when you create ViewModel, you shouldn't use = viewModel() beacause you have use Compose Navigation, = hiltViewModel() should be used instead.
Jetpack Compose + ViewModel in NavGraph
This answer is for people who using Jetpack Compose and navigation (NavGraph)
According to Hilt and Navigation in documentation we have to use hiltViewModel instead of viewModel
Example:
dependencies {
implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
}
// import androidx.hilt.navigation.compose.hiltViewModel
#Composable
fun MyApp() {
NavHost(navController, startDestination = startRoute) {
composable("example") { backStackEntry ->
// Creates a ViewModel from the current BackStackEntry
// Available in the androidx.hilt:hilt-navigation-compose artifact
val viewModel = hiltViewModel<MyViewModel>()
MyScreen(viewModel)
}
/* ... */
}
}
More and fresh information in the source
I faced this issue before and I have fixed it by passing SavedStateHandle to the main constructor of the view model.
class AuthViewModel #ViewModelInject constructor(#Assisted private val savedState: SavedStateHandle) : ViewModel()
For those who checked all the above solutions and still not working then the final check is to delete the Build folder and rebuild the project this will force the compiler to re-create the dagger dependency graph under the hood.
In my case, I have annotated my activity with #AndroidEntryPoint still facing the same issue. I have deleted my build folder and rebuild the project and it is working as expected.
In alpha03, Use the new #HiltViewModel and the normal #Inject now as shown below.
#HiltViewModel
class MyViewModel #Inject constructor(
private val repository: Repository,
private val savedStateHandle: SavedStateHandle
) : ViewModel(), LifecycleObserver {
// Some code
}
I have also faced this problem today, I tried all the possible fixes suggested but it was impossible to remove the error. I just post my solution here in case someone has the same problem in the future.
In my case I have a multi-module project, with 'UI module', 'ViewModel module' and 'Use-cases module'. The error on my side was that I was not importing all the modules in the application's gradle module, I was only importing UI module. I found this note in the Android developers website regarding hilt implementation:
Note: Because Hilt's code generation needs access to all of the Gradle modules that use Hilt, the Gradle module that compiles your Application class also needs to have all of your Hilt modules and constructor-injected classes in its transitive dependencies.
When I imported all the modules that were needed to generate the DI graph, this crash disappeared.
for me resolvec when moved = viewModel() from Compose function to Activity
I have a project which I use Dagger2 & kotlin, I imported "org.jetbrains.kotlin:kotlin-stdlib:1.3.50” in my build.gradle and I was getting this Error:
w: Runtime JAR files in the classpath should have the same version.
These files were found in the classpath:
w:
/Users/macbook/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jre7/1.1.51/8b5933578dc55f32cfc1a25f1db6371e4161fb8f/kotlin-stdlib-jre7-1.1.51.jar:
kotlin-stdlib-jre7 is deprecated. Please use kotlin-stdlib-jdk7
instead
So therefore I changed from "kotlin-stdlib" to "kotlin-stdlib-jdk7" using:
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50” and I am still getting this error.
I then made some research and found this URL: warning: Kotlin runtime JAR files in the classpath should have the same version
I found the usage of kotlin-reflect: "implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.50"
I added it to my build.gradle BUT I still have the same errors
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7: 1.3.50"
implementation "org.jetbrains.kotlin:kotlin-reflect: 1.3.50"
This is my BaseApplication class below, which I am calling the Dagger components :
class BaseApplication : Application() {
companion object {
#JvmStatic lateinit var netComponent: NetComponent
#JvmStatic lateinit var exampleComponent: ExampleComponent
}
override fun onCreate() {
super.onCreate()
netComponent = DaggerNetComponent.builder()
.appModule(AppModule(this))
.netModule(NetModule())
.build()
exampleComponent = DaggerExampleComponent.builder()
.netComponent(netComponent)
.retrofitModule(RetrofitModule())
.exampleModule(ExampleModule(this))
.build()
}
}
Data binding setup:
apply plugin: 'kotlin-kapt'
android {
dataBinding {
enabled = true
}
}
dependencies {
kapt "com.android.databinding:compiler:3.1.0"
}
The fragment class which uses data binding:
class LandingFragment : Fragment(), Injectable {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val dataBinding = LandingFragmentBinding.inflate(inflater, container, false)
return dataBinding.root
}
}
Every time the Espresso test is run for this fragment, I get the following exception:
java.lang.NoClassDefFoundError: android.databinding.DataBinderMapperImpl
at android.databinding.DataBindingUtil.<clinit>(DataBindingUtil.java:32)
at com.sc.databinding.LandingFragmentBinding.inflate(LandingFragmentBinding.java:42)
at com.sc.ui.landing.LandingFragment.onCreateView(LandingFragment.kt:32)
...
A bit late, but I resolved this issue by adding DataBinding compiler with kapt as a test dependency:
kaptAndroidTest 'androidx.databinding:databinding-compiler:3.3.2'
Or the version not from AndroidX if your project is not using Jetpack yet.
Add
kaptTest "androidx.databinding:databinding-compiler:+"
to dependencies on build.gradle files of all your modules.
I run into this very error. I did 2 things:
1. Added kaptAndroidTest 'androidx.databinding:databinding-compiler:3.5.1' in gradle
2. Used the databinding, that is to say, I create a fake bool variable and injected it for real in a view. It would seem that you cannot just use databinding for retrieving the views instead of issuing the dreaded findViewById. You have to use it at least once in your module. Once you use it you are fine for all the other classes in your module.
I have the same this issue, and fixed by adding
kapt {
generateStubs = true
}
in build.gradle app (all module if using dataBinding)
apply plugin: 'kotlin-kapt'
android {
...
dataBinding {
enabled = true
}
}
kapt {
generateStubs = true
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
...
implementation "androidx.core:core-ktx:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
kapt "com.android.databinding:compiler:$android_plugin_version"
}
In build.gradle project
buildscript {
ext.kotlin_version = '1.3.70'
ext.android_plugin_version = '3.5.2'
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:$android_plugin_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Add dataBinding = true to android { } in your build.gradle file, and everything will be fine, hope this help..
build.gradle:
android {
// skip ..
buildFeatures {
//noinspection DataBindingWithoutKapt
dataBinding = true
viewBinding true
}
// skip ..
}
Try to add the android-apt plugin as per this stackoverflow answer:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
I have decided a few weeks ago to start converting my existing app (I wrote it in java) to kotlin.
I converted only one activity and I wanted to check the functionality before I will move forward and change all of my classes.
Unfortunately I'm getting the following error:
Unresolved reference: DaggerSearchComponent
This is how I resolve my dependencies with this activity, this code will be called from within the onCreate method:
SearchActivity.kt
override fun resolveDependency() {
DaggerSearchComponent.builder()
.applicationComponent(applicationComponent)
.searchModule(SearchModule(this))
.build().inject(this)
}
SearchComponent.java
#PerActivity
#Component(modules = SearchModule.class, dependencies = ApplicationComponent.class)
public interface SearchComponent {
void inject(SearchActivity activity);
}
The component of this activity is still written with java, although I tried to convert it with kotlin but I got the same error.
I added kotlin plugin in my build.gradle (Module:app)
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'
build.gradle(project)
apply plugin: 'kotlin-kapt'
buildscript {
ext.kotlin_version = '1.3.21'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
I did not find any other useful information except for adding kotlin plugin the app gradle file but it did not work for me.
When using dagger2 is it possible to use kotlin with java or should I need to convert every component/module into kotlin before testing it?
Make sure you added these dependencies in module gradle:
implementation "com.google.dagger:dagger:$rootProject.dagger2Version"
implementation "com.google.dagger:dagger-android:$rootProject.dagger2Version"
implementation "com.google.dagger:dagger-android-support:$rootProject.dagger2Version"
kapt "com.google.dagger:dagger-android-processor:$rootProject.dagger2Version"
kapt "com.google.dagger:dagger-compiler:$rootProject.dagger2Version"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$rootProject.dagger2Version"
And add
apply plugin: 'kotlin-kapt'
also to module level gradle.
Make changes in SearchComponent(Kotlin)
#PerActivity
#Component(modules = [SearchModule.class::class], dependencies = [ApplicationComponent::class])
interface SearchComponent {
fun inject(application: Application)
}
For Dagger2 release , i plan to split the module into few small module for re-use on other projects.
Application Module contains many things, i can group it into three type.
Type A related, Type B related, Type C related.
so i want to put it into three different module , therefore it can re-use part of it if need on other projects.
Reference from the Google's Fork
build.gradle for Application
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.1.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenCentral()
}
}
build.gradle for app module
apply plugin: 'com.neenbedankt.android-apt'
//below lines go to dependenc
compile 'com.google.dagger:dagger:2.0'
apt 'com.google.dagger:dagger-compiler:2.0'
provided 'org.glassfish:javax.annotation:10.0-b28'
after above steps , i am going to create my application module
#dagger.Module
public class MyApplicationModule {
void inject(MyApplication application);
}
and my component
#Singleton
#Component(
modules = {TypeA.class, TypeB.class, TypeC.class})
public interface MyApplicationComponent {
When i use it on activity , it looks like
#Component(dependencies = MyApplicationComponent.class,
modules = ActivityModule.class)
public interface ActivityComponent {
void injectActivity(SplashScreenActivity activity);
There are compile issue
Error:(22, 10) error: XXXXXX cannot be provided without an
#Provides- or #Produces-annotated method.
com.myapplication.mXXX
[injected field of type:
XXX]
i guess there are something wrong when i config that Activity Component extends from application component.
my purpose is some singleton object inside Application Component , activity will inject same object to reduce some object create every time on activity.
is my design wrong?? or any other things need to do for config??
find out the root cause is come from #Scope
need to expose the type for other sub-component usage
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface ActivityScope {
}
if we want to expose the Context for application ,
#Singleton
#Component(
{TypeA.class, TypeB.class, TypeC.class})
public interface MyApplicationComponent {
void inject(MyApplication application);
#ForApplication
Context appContext();
when my sub-component want to extend this
#ActivityScope
#Component(dependencies = MyApplicationComponent.class,
modules = ActivityModule.class)
public interface ActivityComponent extends MyApplicationComponent {
void injectActivity(Activity activity);
i think it is a great thing for Dagger2 , let you manually expose the object you need to use , code become more traceable.