I'm using dagger2 for DI, and developing for a single activity. So I did inject a fragment when start main activity, and the fragment also inject this viewmodel. But the problem is occured when I inject a viewmodel in dagger fragment. If I don't use a constuctor #Inject in dagger fragment, ViewModel is working well But can't Inject at MainActivity. If I use a constuctor #Inject in dagger fragment, ViewModel is not working and got the error like this
A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution
Should I give up one?
MainActivity
#Inject
lateinit var myFolderFragment:MyFolderFragment
myFolderFragment:MyFolderFragment
class MyFolderFragment #Inject constructor(): DaggerFragment() {
#Inject
lateinit var viewModelFactory : ViewModelProvider.Factory
private val viewModel by viewModels<MyFolderViewModel> { viewModelFactory }
private lateinit var binding : FragmentMyfolderBinding
private var mActivity:Activity?=null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentMyfolderBinding.inflate(layoutInflater, container, false).apply {
viewmodel = viewModel
}
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
binding.lifecycleOwner = this.viewLifecycleOwner
}
You cannot inject dependencies into Fragment via it's constructor. Activities, BroadcastReceivers, Services, ContentProviders and Fragments require default constructor because OS creates instances of that classes using Reflection API.
In my opinion you have 3 ways to solve this :
way 1 (Hardest One) - Use FragmentFactory
way 2 (Easy to Undestand) - Direct inject in onCreateView method, it will look like this
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentMyfolderBinding.inflate(layoutInflater, container, false).apply {
(context as MyApplication).component.inject(this)
viewmodel = viewModel
}
return binding.root
}
way 3 (Less code) - Instead of Dagger2 you can use Hilt that will hide all magic that you you manually writing when use way 2
Related
I am studying ViewModel to apply it to MVVM design pattern.
There was a method using by viemodels() and a method using ViewModelProvider.Factory in view model creation.
by viewModels() creates a ViewModel object.
ViewModelProvider.Factory also creates Viewmodel objects.
What is the difference between these two?
In addition, in some sample code, I saw the code in comment 3, which uses by viewModels() and factory together. What does this mean?
class WritingRoutineFragment : Fragment() {
private val viewModel: WriteRoutineViewModel by viewModels() // 1
private lateinit var viewModelFactory: WriteRoutineViewModelFactory
// private val viewModel: WriteRoutineViewModel by viewModels(
// factoryProducer = { viewModelFactory } // 3.What does this code mean?
// )
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
_binding = FragmentWritingRoutineBinding.inflate(inflater, container, false)
viewModelFactory = WriteRoutineViewModelFactory()
// viewModel = ViewModelProvider(this, viewModelFactory).get(WriteRoutineViewModel::class.java) // 2
return binding.root
}
If your ViewModel has a zero-argument constructor, or if it has a constructor where its only argument is of type Application and it's a subclass of AndroidViewModel, then you do not need a factory. (Or if your constructor is either of the above plus SavedStateHandle.) A view model factory is a class that is able to instantiate your ViewModel that has a more complicated constructor.
When instantiating your ViewModel without using a delegate, you have to use a lateinit var for the property because you can't instantiate it until onCreateView.
If your ViewModel had no need for a factory, the process of doing it without a delegate would look like this:
class WritingRoutineFragment : Fragment() {
private lateinit var viewModel: WriteRoutineViewModel
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
//...
viewModel = ViewModelProvider(this, viewModelFactory).get(WriteRoutineViewModel::class.java)
//...
}
}
and if it did need a factory, it would look like this, where you have to instantiate a factory and pass it to the ViewModelProvider constructor:
class WritingRoutineFragment : Fragment() {
private lateinit var viewModel: WriteRoutineViewModel
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
//...
viewModel = ViewModelProvider(this, WriteRoutineViewModelFactory()).get(WriteRoutineViewModel::class.java)
//...
}
}
The delegate allows you to do this more concisely in a val right at the declaration site so you don't have to do any setup of your view model property in onCreateView. It will lazily create the ViewModel the first time the property is used. The advantage is more concise and clearer code (lateinit var splits the property from its declaration and makes it mutable even though it will never change).
So the above code when no factory is needed looks like:
class WritingRoutineFragment : Fragment() {
private val viewModel: WriteRoutineViewModel by viewModels()
}
and if you do need a factory it will look like this. You pass it a function that instantiates the factory, which is easily done with a lambda:
class WritingRoutineFragment : Fragment() {
private val viewModel: WriteRoutineViewModel by viewModels { WriteRoutineViewModelFactory() }
}
The code in your example has an extra property just to hold the factory, which is an unnecessary complication since you'll never need to access it directly. It's also quite odd that the factory in your example has an empty constructor, because if the factory doesn't have any state, then it has no data to pass to the ViewModel constructor.
I'm looking through the tutorial for Android room with a view, and trying to extend their model for using ViewModels to multiple fragments, but not really sure how.
MyApplication
class myApplication : Application() {
companion object {
var database: myDatabase? = null
var repository: myRepository? = null
}
override fun onCreate() {
super.onCreate()
database = MyDatabase.getInstance(this)
repository = MyRepository(database!!.myDatabaseDao)
}
}
MyViewModel
class MyViewModel(private val repository: MyRepository) : ViewModel() {
val allWords: LiveData<List<Words>> = repository.allWords.asLiveData()
fun insert(word: Word) = viewModelScope.launch {
repository.insert(word)
}
}
class MyViewModelFactory(private val repository: MyRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return MyViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
HomeFragment
class HomeFragment : Fragment() {
private val myViewModel: MyViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
var rootView = inflater.inflate(R.layout.fragment_home, container, false)
return rootView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
myViewModel.allWords.observe(viewLifecycleOwner) { words ->
// Update the cached copy of the words in the adapter.
words.let { Log.d("fragment", it.toString()) }
}
}
}
I have a couple of other fragments that will hopefully share the same ViewModel as HomeFragment. I've tried many different approaches, such as using
myViewModel = ViewModelProviders.of(activity!!).get(MyViewModel::class.java)
but all of them give me Caused by: java.lang.InstantiationException: java.lang.Class<com.example.tabtester.ViewModels.MyViewModel> has no zero argument constructor. I can't find any SO posts or documentation that shows me how to provide a constructor in Kotlin.
Also conceptually I can't find any description for what exactly is happening and how the viewmodel is being constructed (and by what). In the Room with a View tutorial, the example given is in MainActivity:
private val wordViewModel: WordViewModel by viewModels {
WordViewModelFactory((application as WordsApplication).repository)
}
This makes sense, to me; you're using the Factory to instantiate a ViewModel to use in the MainActivity. But for any description of how to use ViewModels in Fragments, I don't see where the ViewModel is being constructed. If you have multiple fragments who is constructing the ViewModel? If I use Fragments then does that mean I also need an activity to construct the ViewModel, then somehow share between the Fragments?
Would appreciate any help, or documentation that explains this more clearly.
The underlying APIs of by viewModels(), by activityViewModels() and the (now deprecated) ViewModelProviders.of() all feed into one method: the ViewModelProvider constructor:
ViewModelProvider(viewModelStore: ViewModelStore, factory: ViewModelProvider.Factory)
This constructor takes two parameters:
The ViewModelStore controls the storage and scoping of the ViewModel you create. For example, when you use by viewModels() in a Fragment, it is the Fragment which is used as the ViewModelStore. Similarly, by activityViewModels() uses the Activity as the ViewModelStore.
The ViewModelProvider.Factory controls the construction of the ViewModel if one has not already been created for that particular ViewModelStore.
Therefore if you need a custom Factory, you must always pass that Factory into all places that could create that ViewModel (remember, due to process death and recreation, there's no guarantee that your HomeFragment will be the first fragment to create your ViewModel).
private val myViewModel: MyViewModel by activityViewModels() {
MyViewModelFactory(MyApplication.repository!!)
}
As long as you're using activityViewModels(), the storage of your ViewModel will always be at the activity level, no matter what Factory you are using.
My Fragment:
class FirstFragment : Fragment() {
private lateinit var binding: FragmentFirstBinding
private lateinit var viewModelFactory: FirstViewModelFactory
private lateinit var viewModel: FirstViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_first, container, false)
viewModelFactory = FirstViewModelFactory(requireActivity().application, this.lifecycle) //<- Lifecycle object
viewModel = ViewModelProvider(this, viewModelFactory).get(FirstViewModel::class.java)
return binding.root
}
}
My ViewModel:
class FirstViewModel(application: Application, lifecycle: Lifecycle) : AndroidViewModel(application), LifecycleObserver {
init {
lifecycle.addObserver(this)
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private fun showOnStopMessage() {
Log.v("xxx", "onStop called!!")
}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
private fun showOnStartMessage() {
Log.v("xxx", "onStart called!!")
}
}
The above setup works well in no-configuration-change environment, showOnStopMessage() gets called when app goes to the background, and showOnStartMessage() gets called when the app is brought back to the foreground.
The problem is, when configuration-change happens (like rotating the screen), those functions are not being called any more.
Why this happens? How to detect and "survive" configuration-change? Thanks in advance.
As far as I understand, the problem is that your ViewModel is created only once (as it should be) and it only adds the lifecycle of the first fragment as a LifecycleObserver. When you rotate the screen, the same ViewModel is returned and it'll still try to react to the changes of the old Fragment, which won't happen.
I'd suggest not dealing with lifecycle inside the ViewModel at all (remove the related code from the Factory and from the ViewModel). Just call:
lifecycle.addObserver(viewModel)
right after the ViewModel is obtained, inside onCreateView.
I know that is not a best practice to pass a context to a ViewModel. but I wonder is it okay to get a context instance as a local parameter of a function in the ViewModel?
because in this case the function use the context and release that context reference by the end of the function.
and please assume that we don't want use AndroidViewModel to get application Context.
for example:
class MyViewModel : ViewModel(){
initColors(context:Context){
//do something with context like getting colors from resourcse
}
}
and in fragment:
class Myfrgament:Fragment(){
private val viewModel: LessonFragmentViewModel by viewModels{}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel.initColors(requireContext())
}
}
What is the proper way to keep the actual app state in MVVM? A simple example to describe what I mean: I have two fragments and one global variable or object of my Class. I can change this variable or object on both fragments. Where should I keep this in code?
The most easiest way is to use KTX extension function activityViewModels<VM : ViewModel>
see here.
From the doc:
Returns a property delegate to access parent activity's ViewModel ...
It will retrieve the ViewModel instance provided by the ViewModelProviders of the activity the fragments are attached to.
So any change on the view model instance will be reflected on all fragments.
Here a simple example:
class MVModel: ViewModel() {
var count = MutableLiveData(0)
fun increment() {
count.value = count.value!!.plus(1)
}
}
class MFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentMBinding.inflate(inflater, container, false)
val viewModel by activityViewModels<MVModel>()
binding.lifecycleOwner = this // <-- this enables MutableLiveData update the UI
binding.vm = viewModel
return binding.root
}
}
You can make shared view model where all your components will access easily