I'm following a tutorial where a ViewModel extends an abstract class in order to use coroutines, this is the class that extends:
abstract class BaseViewModel(application: Application) : AndroidViewModel(application), CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCleared() {
super.onCleared()
job.cancel()
}}
And this is the ViewModel:
class ViewModel(application: Application) : BaseViewModel(application) {}
So in MainActivity I'm trying to instantiate the class like this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this)[ViewModel::class.java]}
In the tutorial the guy perfectly does this but when I try to run the application it throws me an exception:
Caused by: java.lang.RuntimeException: Cannot create an instance of class com.name.nameapp.main.viewmodel.ViewModel
I feel like I'm missing something can you guys point what it is, if you need more information I'll gladly post it
Your ViewModel is child of AndroidViewModel which require an Application object. So you will have to provide the Factory class in order to instantiate the ViewModel. Like so:
val viewModelProvider = ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory(application)
)
viewModel = viewModelProvider[MainViewModel::class.java]
If you are using the fragment library from Jetpack
implementation "androidx.fragment:fragment-ktx:1.2.5"
You can use property delegation like so:
val viewModel: ViewModel by viewModels()
Related
Currently, I have something similar to:
interface MyViewModel {
val elements: StateFlow<List<MyElements>>
val visible: StateFlow<Boolean>
fun onClick(button: MyElement)
}
#HiltViewModel
class MyViewModelImpl #Inject constructor(
private val myUseCases: MyUseCases,
) : ViewModel(), MyViewModel {
override val elements: StateFlow<List<MyElements>> = myUseCases.getList()
override val visible: MutableStateFlow<Boolean> by lazy { MutableStateFlow(false) }
override fun onClick(button: MyElement) {
myUseCases.click(button)
...
}
}
#Composable
fun MyComposable(
myViewModel: MyViewModel = hiltViewModels<MyViewModelImpl>(),
) { ... }
I would like to know if there is some way to completely decouple my Composable from my ViewModel implementation class (i.e. remove the <MyViewModelImpl> in hiltViewModels()). I've tried a few different things, such as making the interface an abstract class and creating #Binds and #Provides methods in Dagger Hilt #Modules, but I can't seem to get it right. Ideally, the Composable should have no knowledge of the implementing ViewModel class so that I can change it for testing etc. This would be a big help because I have several composables that inject the same ViewModel interfaces.
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 use the viewmodel in my activity but my app crashes the error "Cannot create an instance of class" from the viewmodel. The ViewModel is like this:
class MyViewModel#Inject constructor(val application: Application) : ViewModel() {
//...
}
In my activity, I have this:
class Activity: BaseActivity(){
val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
If I delete the constructor, my app works but I need to get packageName, so I need context or application.
Why I'm getting this error? Any idea?
You can use AndroidViewModel
class MyViewModel#Inject constructor(val application: Application) : AndroidViewModel(application)
If you using Dagger-hilt may b you can't add #HiltViewModel. also check in your activity whether this tag (#AndroidEntryPoint) is added or not.
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 have the following class signature:
abstract class BaseActivity<E : ViewModel> : AppCompatActivity() {
protected lateinit var viewModel: E
}
Now I want to initialize my viewModel in a generic way using ViewModelProvider, so:
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(MyViewModel::class)
Given that MyViewModel class will be provided in the generic type, I'd say this could potentially be abstracted into the BaseActivity so I dont have to do it for every Activity that extends it.
I tried with:
inline fun <reified E : ViewModel> getViewModelClass() = E::class.java
But then when doing:
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(getViewModelClass())
I get Cannot use E as reified type paramter. Use class instead
Is there a solution to this?
E in BaseActivity can't be reified, so you can't pass it to any methods which take a reified E.
Your best option may just be to accept the class as a constructor parameter.
abstract class BaseActivity<E : ViewModel>(private val modelClass: Class<E>) : AppCompatActivity() {
protected lateinit var viewModel: E
... viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(modelClass)
}
If BaseActivity wasn't abstract, you could add a factory method:
// outside BaseActivity class itself
fun <reified E : BaseModel> BaseActivity() = BaseActivity(E::class.java)
but it wouldn't help when extending it.
You ca do it in this way:
abstract class BaseActivity<E : ViewModel> : AppCompatActivity() {
protected lateinit var viewModel: E
abstract fun getViewModel():E
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(getViewModel())
}
}
Now, you can extend any class from BaseActivity and override the getViewModel() function returning the respective ViewModel class.
Hope this helps.
EDIT
Try this once:
inline fun <reified E> getViewModelClass(): Class<E> {
return E::class.java
}
and use it like this:
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(getViewModelClass())
https://stackoverflow.com/a/52107111/8832537
You should check this guy's solution. The only downside of his approach is he uses Reflection API to get the generic parameter. I researched a lot but didn't find a solution that doesn't use reflection. If you did find it, let me know. That would be more convenient.