I am trying to use hilt for my project which contains dynamic features. I am facing an error that I cannot fully understand why. I get an error like this:
java.lang.ClassCastException: com.social.analysis.DaggerApp_HiltComponents_ApplicationC$ActivityRetainedCImpl$ActivityCImpl$FragmentCImpl cannot be cast to com.social.login.intro.IntroFragment_GeneratedInjector
at com.social.login.intro.Hilt_IntroFragment.inject(Hilt_IntroFragment.java:94)
at com.social.login.intro.Hilt_IntroFragment.initializeComponentContext(Hilt_IntroFragment.java:58)
at com.social.login.intro.Hilt_IntroFragment.onAttach(Hilt_IntroFragment.java:50)
at androidx.fragment.app.Fragment.onAttach(Fragment.java:1602)
at com.social.login.intro.Hilt_IntroFragment.onAttach(Hilt_IntroFragment.java:40)
My ViewModel in LOGÄ°N MODULE (dynamic features)
class IntroViewModel #Inject constructor(): ViewModel() {
// TODO: Implement the ViewModel
}
My Fragment in LOGIN MODULE
#AndroidEntryPoint
class IntroFragment : BaseFragment<FragmentIntroBinding, IntroViewModel>(
R.layout.fragment_intro
) {
companion object {
fun newInstance() = IntroFragment()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onInitDataBinding() {
viewBinding.viewModel = viewModel
}
}
My Base Fragment in UI Module
abstract class BaseFragment <B: ViewDataBinding, M: ViewModel>(
#LayoutRes
private val layoutId: Int
): Fragment() {
#Inject
lateinit var viewModel: M
lateinit var viewBinding: B
...
My Application Class in App Module
#HiltAndroidApp
class App : SplitCompatApplication() {
}
My Main Activity in App Module
#AndroidEntryPoint
class MainActivity : AppCompatActivity()
I call the IntroFragment from the App module. Then the application crashes.
The project structure looks like this:
From an answer to a similar question:
Delete the .gradle directory (in the project base directory)
Invalidate Caches and restart Android Studio.
Related
I am using a Room Database with AndroidViewModel (without Factory)
class RoomModel(app: Application) : AndroidViewModel(app) {
// ....
}
and not sure about how to correctly initialize that and also, if I could use one-time initilization for whole app.
I have two ways to deal with initializations which seem to work ok:
Initializing in onViewCreated():
...
private lateinit var database: RoomModel
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
database = ViewModelProvider(this)[RoomModel::class.java]
...
}
Initializing by activityViewModels():
...
private val mData: RoomModel by activityViewModels()
...
The both seem to work, but not sure which one would never lead to an app crash at some point.
Also, I would like to know, if it's a good practce using one shared reference variable of my RoomModel declared and initialized in a base Fragment that is used by other fragments like this:
class BaseFragment : Fragment() {
...
lateinit var database: RoomModel
// Or
val database: RoomModel by activityViewModels()
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
database = ViewModelProvider(this)[RoomModel::class.java]
...
}
}
Some other fragments extended by BaseFragment() like this:
class FragmentA : BaseFragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
// use the reference variable database to observe or add or edit data
}
}
and
class FragmentB : BaseFragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
// use the reference database to observe or add or edit data
}
}
and so on...
In this way I have only to init the Room once and use it without reinitializing in each fragmet I need to access to. Is that a goog idea or I rather need to use individual declaration and initialization of RoomModel in each Fragment I need access to?
Many thanks in advance.
Your Room Database instance should be a singleton. The instance should live as long as the application itself. It is costly to initialize it every time you need to use it. You should initialize Room Database in the application and each ViewModel that needs Room Database instance will get it from the application as a dependency. Or you can provide the Room Database as a dependency to your ViewModel, but then you need to create a factory for your ViewModel. You can also use a Dependency Injection library to manage it for you, but it can be overwhelming at first.
Without using any dependency injection library, you can see the example below.
Override the Application, and don't forget to declare it in the AndroidManifest.xml file.
AndroidApp.kt
class AndroidApp : Application() {
lateinit var myRoomDatabase: MyRoomDatabase
override fun onCreate() {
super.onCreate()
myRoomDatabase = Room
.databaseBuilder(applicationContext, MyRoomDatabase::class.java, "my-room-database")
.build()
}
}
AndroidManifest.xml
...
<application
android:name=".AndroidApp"
...
>
...
When you extends the AndroidViewModel you have the Application instance as a dependency. Cast it to AndroidApp and now you can get myRoomDatabase.
MainViewModel.kt
class MainViewModel(app:Application) : AndroidViewModel(app) {
private val myRoomDatabase = (app as AndroidApp).myRoomDatabase
...
}
FragmentAViewModel.kt
class FragmentAViewModel(app:Application) : AndroidViewModel(app) {
private val myRoomDatabase = (app as AndroidApp).myRoomDatabase
...
}
FragmentBViewModel.kt
class FragmentBViewModel(app:Application) : AndroidViewModel(app) {
private val myRoomDatabase = (app as AndroidApp).myRoomDatabase
...
}
I have a fragment class as follows:
class MainFragment : Fragment(R.layout.main_fragment) {
#Inject lateinit var runner: Runner
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
runner.runProcess()
}
}
Here is my Runner.java file:
public class Runner {
#Inject
public Runner(#ApplicationContext Context applicationContext) {
// some setup code
}
public runProcess() {...}
}
I am getting an error at the runner.runProcess() line saying that lateinit property runner is not initialized. I am using Hilt in my Android app. How can I go about fixing this?
MainActivity.kt:
#AndroidEntryPoint
class MainActivity #Inject constructor() : AppCompatActivity(R.layout.activity_main) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
supportFragmentManager.commit {
setReorderingAllowed(true)
add<MainFragment>(R.id.fragment_container_view);
}
}
}
}
Found the issue. I have to add the #AndroidEntryPoint annotation to my fragment as well, despite the fragment being added to the main activity which has this annotation.
For future reference, the #AndroidEntryPoint annotation should be provided for every android class which requires dependency injection. It marks the entry points for hilt to inject modules. It is not to be confused with the entry point of the app i.e., the launch activity.
Source
I am new in adroid , so I have a simple project, I want to create simple register project, so I have viewmodel in my project and I amusing Hilt library also in there, and when I build project it is throw an error for
myViewModel = ViewModelProvider(this)[MyViewModel::class.java]
as a "Cannot create an instance of class com.app.myapp.viewModel", I do not know what I missed?
class Register : ComponentActivity() {
private lateinit var myViewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myViewModel = [ViewModelProvider(this)::class.java]
setContent {
RegisterScreen(myViewModel)
}
}
}
#Composable
fun RegisterScreen(
myViewModel: MyViewModel
) {
}
Reasons may cause system can not create viewModel:
Your viewModel class is not Public
Your package name which contains viewModel contains special keywords (such a "package.new.feature")
If you are using dagger hilt you should putt annotation #HiltViewModel above the class declaration and create constructor like
#HiltViewModel
class viewModel #Inject constructor() : ViewModel()
With the dagger hilt You should use hiltViewModel() function to create instance for compose instead of viewModel()
dependency: androidx.hilt:hilt-navigation-compose
#Composable
fun MyExample (viewModel: MyViewModel = hiltViewModel())
Your ViewModel class does not extend from androidx.lifecycle.ViewModel
You should create your ViewModel class extending from the ViewModel, something like RegisterViewModel.
Take a look at the documentation for more info:
https://developer.android.com/topic/libraries/architecture/viewmodel
You are trying to create a view model from the base class ViewModel. it doesn't work like this
You need to create your own viewmodel class and extend it from the base class ViewModel like this
class MyViewModel : ViewModel() {
}
So your code will be like
class MyViewModel : ViewModel() {
// your implementation
}
class Register : ComponentActivity() {
private lateinit var viewModel: MyViewModel // changes to this line
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this)[MyViewModel::class.java] // changes to this line
setContent {
RegisterScreen(viewModel)
}
}
}
BUT if you are using compose you should look at the integration between viewmodel and compose
to make your composable use the viewModel without you creating it then passing it to the composable
#Composable
fun MyExample(
viewModel: MyViewModel = viewModel()
) {
// use viewModel here
}
I'm trying to make a shared injected view model between a fragment and an activity using the Jetpack tutorial.
The shared view model is successfully injected into the parent MyActivity but when the child is rendered, the application crashes due to dependency injection failure. I have provided the code below that created the issue.
Providing the Session Manager:
#InstallIn(ApplicationComponent::class)
#Module
class AppModule {
#Provides
#Singleton
fun provideSessionManager(
networkClient: NetworkClient
): SessionManager {
return SessionManager(networkClient)
}
}
To be injected into the Shared View Model:
class SharedViewModel #ViewModelInject constructor(
private var sessionManager: SessionManager
) : ViewModel() {
var name = MutableLiveData<String>("Shared View Model")
}
And is used by both a parent activity and child fragment.
class MyActionFragment() : Fragment() {
private val viewModel: SharedViewModel by viewModels()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
Timber.d("View Model Name 1: ${viewModel.name.value}") // This line crashes
}
}
class MyActivity : AuthenticatedBaseActivity() {
private val viewModel: SharedViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Timber.d("View Model Name 2: ${viewModel.name.value}") // This line prints
}
}
However, when the code is run, notice the activity created the ViewModel and accessed its values, but when the fragment tried to do the same, the application crashes:
**D/MyActivity: View Model Name 2: Shared View Model**
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.xxx.xxx, PID: 16630
java.lang.RuntimeException: Cannot create an instance of class com.xxx.xxx.ui.main.SharedViewModel
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.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.xxx.xxx.ui.main.MyActionFragment.getViewModel(Unknown Source:2)
at com.xxx.xxx.ui.main.MyActionFragment.onActivityCreated(**MyActionFragment.kt:140**)
at androidx.fragment.app.Fragment.performActivityCreated(Fragment.java:2718)
Additionally, when I remove the Hilt dependency injected sessionManager the fragment and view model are created without an issue.
Followed this post with no luck.
Any help on Hilt view model dependency injection with a shared model would be extremely appreciated!! Thanks!
You can use extension function in Fragment:
class MyFragment: Fragment() {
private val viewModel: SharedViewModel by activityViewModels()
}
And in Activity:
class MyActivity : Activity() {
private val viewModel: SharedViewModel by viewModels()
}
You must provide all dependency , In your case NetworkClient not provided
#Module
#InstallIn(ApplicationComponent::class)
object AppModule {
#Singleton
#Provides
fun provideSessionManager(
networkClient: NetworkClient
): SessionManager = SessionManager(networkClient)
#Singleton
#Provides
fun provideNetworkClient() = NetworkClient()
}
In the Activity or Fragment use #AndroidEntryPoint
#AndroidEntryPoint
class MyActionFragment() : Fragment()
#AndroidEntryPoint
class MyActivity : AuthenticatedBaseActivity()
To share data between activity and fragments. use the below code. Hilt doc didn't work for me also.
In Activity
private val vm by viewModels<StartVM>()
In Fragment
private val vm: StartVM by lazy {
obtainViewModel(requireActivity(), StartVM::class.java, defaultViewModelProviderFactory)
}
Kotlin extension
fun <T : ViewModel> Fragment.obtainViewModel(owner: ViewModelStoreOwner,
viewModelClass: Class<T>,
viewmodelFactory: ViewModelProvider.Factory
) =
ViewModelProvider(owner, viewmodelFactory).get(viewModelClass)
I am facing this issue in multi module android project with HILT.
kotlin.UninitializedPropertyAccessException: lateinit property repository has not been initialized in MyViewModel
My modules are
App Module
Viewmodel module
UseCase Module
DataSource Module
'App Module'
#AndroidEntryPoint
class MainFragment : Fragment() {
private lateinit var viewModel: MainViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
viewModel.test()
}}
'ViewModel Module'
class MainViewModel #ViewModelInject constructor(private val repository: MyUsecase): ViewModel() {
fun test(){
repository.test()
}}
'UseCase Module'
class MyUsecase #Inject constructor() {
#Inject
lateinit var feature: Feature
fun doThing() {
feature.doThing()
}
#Module
#InstallIn(ApplicationComponent::class)
object FeatureModule {
#Provides
fun feature(realFeature: RealFeature): Feature = realFeature
}
}
'DataSource Module'
interface Feature {
fun doThing()
}
class RealFeature : Feature {
override fun doThing() {
Log.v("Feature", "Doing the thing!")
}
}
Dependencies are
MyFragment ---> MyViewModel ---> MyUseCase ---> DataSource
what i did wrong with this code pls correct it.
above your activity class you must add annotation #AndroidEntryPoint
as below:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
In addition to moving all your stuff to constructor injection, your RealFeature isn't being injected, because you instantiate it manually rather than letting Dagger construct it for you. Note how your FeatureModule directly calls the constructor for RealFeature and returns it for the #Provides method. Dagger will use this object as is, since it thinks you've done all the setup for it. Field injection only works if you let Dagger construct it.
Your FeatureModule should look like this:
#Module
#InstallIn(ApplicationComponent::class)
object FeatureModule {
#Provides
fun feature(realFeature: RealFeature): Feature = realFeature
}
Or with the #Binds annotation:
#Module
#InstallIn(ApplicationComponent::class)
interface FeatureModule {
#Binds
fun feature(realFeature: RealFeature): Feature
}
This also highlights why you should move to constructor injection; with constructor injection, this mistake wouldn't have been possible.
The problem in the code is that #ViewModelInject doesn't work as #Inject in other classes. You cannot perform field injection in a ViewModel.
You should do:
class MainViewModel #ViewModelInject constructor(
private val myUseCase: MyUsecase
): ViewModel() {
fun test(){
myUseCase.test()
}
}
Consider following the same pattern for the MyUsecase class. Dependencies should be passed in in the constructor instead of being #Injected in the class body. This kind of defeats the purpose of dependency injection.
First, i think you are missing #Inject on your RealFeature class, so the Hilt knows how the inject the dependency. Second, if you want to inject into a class that is not a part of Hilt supported Entry points, you need to define your own entry point for that class.
In addition to the module that you wrote with #Provides method, you need to tell Hilt how the dependency can be accessed.
In your case you should try something like this:
#EntryPoint
#InstallIn(ApplicationComponent::class)
interface FeatureInterface {
fun getFeatureClass(): Feature
}
Then, when you want to use it, write something like this:
val featureInterface =
EntryPoints.get(appContext, FeatureInterface::class.java)
val realFeature = featureInterface.getFeatureClass()
You can find more info here:
https://dagger.dev/hilt/entry-points
https://developer.android.com/training/dependency-injection/hilt-android#not-supported
class MainViewModel #ViewModelInject constructor(private val repository: HomePageRepository,
#Assisted private val savedStateHandle: SavedStateHandle)
: ViewModel(){}
and intead of initializing the viewmodel like this :
private lateinit var viewModel: MainViewModel
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
Use this directly :
private val mainViewModel:MainViewModel by activityViewModels()
EXplanation :
assisted saved state handle : will make sure that if activity / fragment is annotated with #Android entry point combined with view model inject , it will automatically inject all required constructor dependencies available from corresonding component activity / application so that we won't have to pass these parameters while initializing viewmodel in fragment / activity
Make sure you added class path and plugin
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.35'
in Project.gradle
apply plugin: 'dagger.hilt.android.plugin'
in app.gradle