I'm trying to show a Snackbar in MainFragment when I click a button inside DialogFragment.
When I call the send event function (in ViewModel) from the dialog class nothing happens. I don't know what I'm doing wrong but other functions which do similar things in the MainFragment work just fine.
In ViewModel, the clearInput() function which clears the editText in MainFragment works but onEditNoteClicked() doesn't work and nothing happens when I call it.
NoteOptionsDialog class:
#AndroidEntryPoint
class NoteOptionsDialog : BottomSheetDialogFragment() {
private val viewModel: NotesViewModel by viewModels()
private fun setupClickListeners(view: View) {
view.bottom_options_edit.setOnClickListener {
viewModel.onEditNoteClicked()
dismiss()
}
}
NotesViewModel class:
#HiltViewModel
class NotesViewModel #Inject constructor(
private val noteDao: NoteDao,
private val preferencesManager: PreferencesManager,
private val state: SavedStateHandle
) : ViewModel() {
private val notesEventChannel = Channel<NotesEvent>()
val notesEvent = notesEventChannel.receiveAsFlow()
fun onSaveNoteClick() {
val newNote = Note(noteText = noteText, noteLabelId = labelId.value)
createNote(newNote)
}
private fun createNote(newNote: Note) = viewModelScope.launch {
noteDao.insertNote(newNote)
clearInput()
}
private fun clearInput() = viewModelScope.launch {
notesEventChannel.send(NotesEvent.ClearEditText)
}
fun onEditNoteClicked() = viewModelScope.launch {
notesEventChannel.send(NotesEvent.ShowToast)
}
fun onNoteSelected(note: Note, view: View) = viewModelScope.launch {
notesEventChannel.send(NotesEvent.ShowBottomSheetDialog(note, view))
}
sealed class NotesEvent {
object ClearEditText : NotesEvent()
object ShowToast : NotesEvent()
data class ShowBottomSheetDialog(val note: Note, val view: View) : NotesEvent()
}
NotesFragment class:
#AndroidEntryPoint
class NotesFragment : Fragment(R.layout.fragment_home), NotesAdapter.OnNoteItemClickListener,
LabelsAdapter.OnLabelItemClickListener {
private val viewModel: NotesViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?){
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.notesEvent.collect { event ->
when (event) {
is NotesViewModel.NotesEvent.ShowBottomSheetDialog -> {
NoteOptionsDialog().show(childFragmentManager, null)
}
is NotesViewModel.NotesEvent.ClearEditText -> {
et_home_note.text.clear()
}
is NotesViewModel.NotesEvent.ShowToast -> {
Snackbar.make(requireView(), "Snack!", Snackbar.LENGTH_LONG).show()
}
}
Found the solution. For fragments, I should have used by activityViewModels() instead of by viewModels()
private val viewModel: NotesViewModel by activityViewModels()
initialize viewmodel like below in fragments for sharing same instance
private val viewModel: NotesViewModel by activityViewModels()
Error: can't create an instance of the view model class
here is how I'm trying to create it
class MainActivity : AppCompatActivity() {
lateinit var noteViewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
noteViewModel = ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory.getInstance(this.application)
).get(
NoteViewModel::class.java
)
noteViewModel.getAllNotes().observe(this, object : Observer<List<Note>> {
override fun onChanged(t: List<Note>?) {
Toast.makeText(this#MainActivity, t.toString(), Toast.LENGTH_LONG).show()
}
})
}
}
And here is my view model class
class NoteViewModel(application: Application) : AndroidViewModel(application) {
private val repository: NoteRepository = NoteRepository(application)
private val allNotes: LiveData<List<Note>> = repository.getAllNotes()
fun insert(note: Note) {
repository.insert(note)
}
fun delete(note: Note) {
repository.delete(note)
}
fun update(note: Note) {
repository.update(note)
}
fun deleteAll() {
repository.deleteAllNotes()
}
fun getAllNotes(): LiveData<List<Note>> = allNotes
}
everything looks fine, I don't know what's causing the error
You can use the kotlin property delegate "viewModels()" to instantiate your viewmodel
class MainActivity : AppCompatActivity() {
var noteViewModel: NoteViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
noteViewModel.getAllNotes().observe(this, object : Observer<List<Note>> {
override fun onChanged(t: List<Note>?) {
Toast.makeText(this#MainActivity, t.toString(), Toast.LENGTH_LONG).show()
}
})
}
}
Try with the below dependency then you will be able to get kotlin property delegate "viewModels()" in your activity.
dependencies {
val lifecycle_version = "2.3.1"
val arch_version = "2.1.0"
// ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
// LiveData
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
// Lifecycles only (without ViewModel or LiveData)
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version")
// Saved state module for ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version")
implementation "androidx.activity:activity-ktx:1.3.0-beta01"
}
//write below code in your activity for instantiate viewModel
private val noteViewModel: NoteViewModel by viewModels()
//For more detail go through below link
https://developer.android.com/jetpack/androidx/releases/lifecycle#declaring_dependencies
I had the same problem(in java). This solved it:
// old code
mWordViewModel = new ViewModelProvider(this).get(WordViewModel.class);
// new code
mWordViewModel = new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication())).get(WordViewModel.class);
if anyone is having this problem one solution to fix this is just make ViewModel constructor public (Class that extends AndroidViewModel)
I am getting an exception in the first line of the code below
viewModel.homeLiveData.observe(this, Observer { list ->
list?.let {
mList.addAll(list)
adapter.notifyDataSetChanged()
}
})
java.lang.ClassCastException: java.lang.Class cannot be cast to androidx.lifecycle.ViewModel
The Whole code is Below
what is wrong with the cast ? is there anything wrong of I am creating my ViewModel?
My BaseActivity
abstract class BaseActivity<V : ViewModel> : DaggerAppCompatActivity(), HasSupportFragmentInjector {
#Inject
lateinit var fragmentAndroidInjector: DispatchingAndroidInjector<Fragment>
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
#LayoutRes
abstract fun layoutRes(): Int
protected lateinit var viewModel : V
protected abstract fun getViewModel() : Class<V>
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == android.R.id.home)
onBackPressed()
return super.onOptionsItemSelected(item)
}
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(layoutRes())
viewModel = ViewModelProviders.of(this, viewModelFactory).get(getViewModel())
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> = fragmentAndroidInjector
}
then My Activity
class MainActivity : BaseActivity<MainViewModel>() {
override fun layoutRes(): Int = R.layout.activity_main
override fun getViewModel(): Class<MainViewModel> = MainViewModel::class.java
private val mList = mutableListOf<Any>()
private lateinit var adapter: DataAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setSupportActionBar(toolbar)
recyclerView.apply {
layoutManager = GridLayoutManager(applicationContext, 2)
adapter = DataAdapter(context, mList)
}
viewModel.homeLiveData.observe(this, Observer { list ->
list?.let {
mList.addAll(list)
adapter.notifyDataSetChanged()
}
})
viewModel.getHomeItems()
}
and this is my ViewModel
class MainViewModel #Inject constructor() : ViewModel() {
val homeLiveData: MutableLiveData<List<HomeScreenModel>> = MutableLiveData()
fun getHomeItems() {
Handler().post {
val homeModleList = listOf(
HomeScreenModel(R.drawable.ic_launcher_background, MyApplication.instance.getString(R.string.settings))
)
homeLiveData.setValue(homeModleList)
}
}
}
In my opinion, your viewModel property, which you try to access in viewModel.homeLiveData is shadowed by getViewModel() abstract function that you declare in BaseActivity. This is because Kotlin thinks that getXyZ() is a getter for the field xyZ and thus when you access viewModel, the compiler thinks you want to call getViewModel, which is of type Class<V>. I suggest renaming either the function or the property and it should work then.
Check your viewModel factory if you implemented it correctly
I am trying to write a base activity with code like this
abstract class BaseActivity<T : ViewDataBinding, V : BaseViewModel> : AppCompatActivity(), HasSupportFragmentInjector {
val NO_VIEW_MODEL_BINDING_VARIABLE = -1
private lateinit var mViewModel: V
private lateinit var mViewDataBinding: T
#Inject
lateinit var mViewModelFactory : ViewModelProvider.Factory
#Inject
lateinit var mFragmentInjector: DispatchingAndroidInjector<Fragment>
abstract fun getViewModelBindingVariable() : Int
#LayoutRes
abstract fun getLayoutId() : Int
fun getDataBinding() : T {
return mViewDataBinding
}
fun getViewModel() : V {
return mViewModel
}
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
performDataBinding()
provideViewModel()
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return mFragmentInjector
}
private fun performDataBinding() {
mViewDataBinding = DataBindingUtil.setContentView(this, getLayoutId())
if (getViewModelBindingVariable() != NO_VIEW_MODEL_BINDING_VARIABLE) {
setViewModelBindingVariable()
}
}
private fun setViewModelBindingVariable() {
mViewDataBinding.setVariable(getViewModelBindingVariable(), mViewModel)
mViewDataBinding.executePendingBindings()
}
private fun provideViewModel() {
val clazz: Class<V> = getViewModelClass(javaClass)
mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(clazz)
}
private fun getViewModelClass(aClass: Class<*>): Class<V> {
val type = aClass.genericSuperclass
return if (type is ParameterizedType) {
type.actualTypeArguments[1] as Class<V>
} else {
getViewModelClass(aClass.superclass)
}
}
}
and I also using databinding here, the problem I encountered is whenever I tried to reference the viewmodel class in my xml and return the generated value of the viewmodel by using BR.viewModel in getViewModelBindingVariable it gave an error saying that
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property mViewModel has not been initialized
at com.rahmat.app.newsapp.features.base.BaseActivity.setViewModelBindingVariable(BaseActivity.kt:70)
at com.rahmat.app.newsapp.features.base.BaseActivity.performDataBinding(BaseActivity.kt:65)
at com.rahmat.app.newsapp.features.base.BaseActivity.onCreate(BaseActivity.kt:53)
at com.rahmat.app.newsapp.features.main.MainActivity.onCreate(MainActivity.kt:31)
This error didn't happen if I return NO_VIEW_MODEL_BINDING_VARIABLE but this made me cannot reference the viewmodel class that I want in my xml. I think there is some problem in my provideViewModel() function but I still don't know why. Any help would be much appreciated. thank you
Is there a way to pass additional argument to my custom AndroidViewModel constructor except Application context.
Example:
public class MyViewModel extends AndroidViewModel {
private final LiveData<List<MyObject>> myObjectList;
private AppDatabase appDatabase;
public MyViewModel(Application application, String param) {
super(application);
appDatabase = AppDatabase.getDatabase(this.getApplication());
myObjectList = appDatabase.myOjectModel().getMyObjectByParam(param);
}
}
And when I want to user my custom ViewModel class I use this code in my fragment:
MyViewModel myViewModel = ViewModelProvider.of(this).get(MyViewModel.class)
So I don't know how to pass additional argument String param into my custom ViewModel. I can only pass Application context, but not additional arguments. I would really appreciate any help. Thank you.
Edit: I've added some code. I hope it's better now.
You need to have a factory class for your ViewModel.
public class MyViewModelFactory implements ViewModelProvider.Factory {
private Application mApplication;
private String mParam;
public MyViewModelFactory(Application application, String param) {
mApplication = application;
mParam = param;
}
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
return (T) new MyViewModel(mApplication, mParam);
}
}
And when instantiating the view model, you do like this:
MyViewModel myViewModel = ViewModelProvider(this, new MyViewModelFactory(this.getApplication(), "my awesome param")).get(MyViewModel.class);
For kotlin, you may use delegated property:
val viewModel: MyViewModel by viewModels { MyViewModelFactory(getApplication(), "my awesome param") }
There's also another new option - to implement HasDefaultViewModelProviderFactory and override getDefaultViewModelProviderFactory() with the instantiation of your factory and then you would call ViewModelProvider(this) or by viewModels() without the factory.
Implement with Dependency Injection
This is more advanced and better for production code.
Dagger2, Square's AssistedInject offers a production-ready implementation for ViewModels that can inject necessary components such as a repository that handles network and database requests. It also allows for the manual injection of arguments/parameters in the activity/fragment. Here's a concise outline of the steps to implement with code Gists based on Gabor Varadi's detailed post, Dagger Tips.
Dagger Hilt, is the next generation solution, in alpha as of 7/12/20, offering the same use case with a simpler setup once the library is in release status.
Implement with Lifecycle 2.2.0 in Kotlin
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 one factory shared between multiple different view models I'd extend mlyko's answer like this:
public class MyViewModelFactory extends ViewModelProvider.NewInstanceFactory {
private Application mApplication;
private Object[] mParams;
public MyViewModelFactory(Application application, Object... params) {
mApplication = application;
mParams = params;
}
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
if (modelClass == ViewModel1.class) {
return (T) new ViewModel1(mApplication, (String) mParams[0]);
} else if (modelClass == ViewModel2.class) {
return (T) new ViewModel2(mApplication, (Integer) mParams[0]);
} else if (modelClass == ViewModel3.class) {
return (T) new ViewModel3(mApplication, (Integer) mParams[0], (String) mParams[1]);
} else {
return super.create(modelClass);
}
}
}
And instantiating view models:
ViewModel1 vm1 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), "something")).get(ViewModel1.class);
ViewModel2 vm2 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), 123)).get(ViewModel2.class);
ViewModel3 vm3 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), 123, "something")).get(ViewModel3.class);
With different view models having different constructors.
Based on #vilpe89 the above Kotlin solution for AndroidViewModel cases
class ExtraParamsViewModelFactory(
private val application: Application,
private val myExtraParam: String
): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
SomeViewModel(application, myExtraParam) as T
}
Then a fragment can initiate the viewModel as
class SomeFragment : Fragment() {
// ...
private val myViewModel: SomeViewModel by viewModels {
ExtraParamsViewModelFactory(this.requireActivity().application, "some string value")
}
// ...
}
And then the actual ViewModel class
class SomeViewModel(application: Application, val myExtraParam:String) : AndroidViewModel(application) {
// ...
}
Or in some suitable method ...
override fun onActivityCreated(...){
// ...
val myViewModel = ViewModelProvider(this, ExtraParamsViewModelFactory(this.requireActivity().application, "some string value")).get(SomeViewModel::class.java)
// ...
}
I made it a class in which the already created object is passed.
private Map<String, ViewModel> viewModelMap;
public ViewModelFactory() {
this.viewModelMap = new HashMap<>();
}
public void add(ViewModel viewModel) {
viewModelMap.put(viewModel.getClass().getCanonicalName(), viewModel);
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
for (Map.Entry<String, ViewModel> viewModel : viewModelMap.entrySet()) {
if (viewModel.getKey().equals(modelClass.getCanonicalName())) {
return (T) viewModel.getValue();
}
}
return null;
}
And then
ViewModelFactory viewModelFactory = new ViewModelFactory();
viewModelFactory.add(new SampleViewModel(arg1, arg2));
SampleViewModel sampleViewModel = ViewModelProviders.of(this, viewModelFactory).get(SampleViewModel.class);
The proper way is to use a dependency injection framework such as Dagger hilt. If not using a DI framework, then do it with ViewModelFactory.
With Dagger Hilt:
A ViewModel with parameters
#HiltViewModel
class MyViewModel #Inject constructor(
private val myRepository: MyRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() { ... }
A Repository
class MyRepository #Inject constructor(
private val myRemoteDataSource: MyDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) { ... }
A Module for providing the dependencies/parameters so they can be injected into repositories and ViewModels.
#InstallIn(ViewModelComponent::class)
#Module
object MyProvideModule {
#Provides
fun provideMyDataSource(#ApplicationContext context: Context): MyDataSource {
//code to create MyDataSource...
return MyDataSource(context)
}
#Provides
fun provideCoroutineDispatcher(): CoroutineDispatcher {
return Dispatchers.IO
}
}
A module for binding the repository
#Module
#InstallIn(ViewModelComponent::class)
interface RepositoryModules {
#Binds
fun provideMyRepository(repository: MyRepository): MyRepository
}
Initiating Dagger hilt with the application with the #HiltAndroidApp annotation.
#HiltAndroidApp
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
}
}
Getting the ViewModel in activities
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val myViewModel: MyViewModel by viewModels()
// Other code...
}
Getting the ViewModel in fragments
#AndroidEntryPoint
class MyFragment : Fragment() {
private val myViewModel: MyViewModel by activityViewModels()
// Other code...
}
With ViewModelFactory:
A ViewModel with parameter messageDataStore, where MessageDataStore is a DataStore class or it can be anything else that you want to pass into the ViewModel.
class MyViewModel(
private val messageDataStore: MessageDataStore,
): ViewModel() { ... }
The ViewModel factory class for creating ViewModels
/**
* Factory for all ViewModels.
*/
#Suppress("UNCHECKED_CAST")
class ViewModelFactory constructor(
private val messageDataStore: MessageDataStore,
owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
) = with(modelClass) {
when {
isAssignableFrom(MyViewModel::class.java) ->
MyViewModel(messageDataStore)
else ->
throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
} as T
}
The application class for creating the dependencies/parameters
class MyApp : Application() {
val messageDataStore: MessageDataStore
get() = MessageDataStore.getInstance(this)
}
Extension functions for getting the factory class in activities and fragments, MyExt.kt
fun AppCompatActivity.getViewModelFactory(savedInstanceState: Bundle?): ViewModelFactory {
val messageDataStore = (applicationContext as MyApp).messageDataStore
return ViewModelFactory(messageDataStore, this, savedInstanceState)
}
fun Fragment.getViewModelFactory(savedInstanceState: Bundle?): ViewModelFactory {
val messageDataStore = (requireContext().applicationContext as MyApp).messageDataStore
return ViewModelFactory(messageDataStore, this.requireActivity(), savedInstanceState)
}
Getting the ViewMode in activities
class MainActivity : AppCompatActivity() {
private lateinit var myViewModel: MyViewModel
// Other code...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vm by viewModels<MyViewModel> { getViewModelFactory(savedInstanceState) }
myViewModel = vm
// Other code...
}
}
Getting the ViewModel in Fragments.
class MyFragment : Fragment() {
private lateinit var myViewModel: MyViewModel
//Other code...
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val vm by activityViewModels<MyViewModel> { getViewModelFactory(savedInstanceState) }
myViewModel = vm
//Other code...
}
}
(KOTLIN) My solution uses little bit of Reflection.
Lets say you don't want to create the same looking Factory class every time you create new ViewModel class which needs some arguments. You can accomplish this via Reflection.
For example you would have two different Activities:
class Activity1 : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val args = Bundle().apply { putString("NAME_KEY", "Vilpe89") }
val viewModel = ViewModelProviders
.of(this, ViewModelWithArgumentsFactory(args))
.get(ViewModel1::class.java)
}
}
class Activity2 : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val args = Bundle().apply { putInt("AGE_KEY", 29) }
val viewModel = ViewModelProviders
.of(this, ViewModelWithArgumentsFactory(args))
.get(ViewModel2::class.java)
}
}
And ViewModels for those Activities:
class ViewModel1(private val args: Bundle) : ViewModel()
class ViewModel2(private val args: Bundle) : ViewModel()
Then the magic part, Factory class's implementation:
class ViewModelWithArgumentsFactory(private val args: Bundle) : NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
try {
val constructor: Constructor<T> = modelClass.getDeclaredConstructor(Bundle::class.java)
return constructor.newInstance(args)
} catch (e: Exception) {
Timber.e(e, "Could not create new instance of class %s", modelClass.canonicalName)
throw e
}
}
}
In Kotlin, since the caller of the ViewModel and the ViewModel itself run in different coroutines, it is more natural and convenient to pass data between them using kotlinx.coroutines.channels.Channel:
class NewViewModel : ViewModel() {
private val newData: MutableLiveData<Service.DataEntry?> by lazy {
MutableLiveData<Service.DataEntry?>().also {
viewModelScope.launch {
val channel = Service.ParamChannel // type Channel<Params>
val params = channel.receive()
it.value = Service.postSomething(params)
}
}
}
fun getData(): LiveData<Service.DataEntry?> {
return newData
}
}
// Calling code:
val model: NewViewModel by viewModels()
model.getData().observe(this) { newData ->
if (newData != null) {
...
}
else
{
...
}
}
runBlocking {
Service.ParamChannel.send(theParams)
}
This is part of working code which I anonymized for demo purposes.
I wrote a library that should make doing this more straightforward and way cleaner, no multibindings or factory boilerplate needed, while working seamlessly with ViewModel arguments that can be provided as dependencies by Dagger:
https://github.com/radutopor/ViewModelFactory
#ViewModelFactory
class UserViewModel(#Provided repository: Repository, userId: Int) : ViewModel() {
val greeting = MutableLiveData<String>()
init {
val user = repository.getUser(userId)
greeting.value = "Hello, $user.name"
}
}
In the view:
class UserActivity : AppCompatActivity() {
#Inject
lateinit var userViewModelFactory2: UserViewModelFactory2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
appComponent.inject(this)
val userId = intent.getIntExtra("USER_ID", -1)
val viewModel = ViewModelProviders.of(this, userViewModelFactory2.create(userId))
.get(UserViewModel::class.java)
viewModel.greeting.observe(this, Observer { greetingText ->
greetingTextView.text = greetingText
})
}
}
Why not do it like this:
public class MyViewModel extends AndroidViewModel {
private final LiveData<List<MyObject>> myObjectList;
private AppDatabase appDatabase;
private boolean initialized = false;
public MyViewModel(Application application) {
super(application);
}
public initialize(String param){
synchronized ("justInCase") {
if(! initialized){
initialized = true;
appDatabase = AppDatabase.getDatabase(this.getApplication());
myObjectList = appDatabase.myOjectModel().getMyObjectByParam(param);
}
}
}
}
and then use it like this in two steps:
MyViewModel myViewModel = ViewModelProvider.of(this).get(MyViewModel.class)
myViewModel.initialize(param)