Inject viewModel with hilt - android

I want to inject my viewModel inside RecyclerView with Hilt.
It can be inject but viewModel not destroy when recyclerView destroyed.
what is the best way to inject viewModel inside recyclerView with hilt?

The best way to do this is to create separate adapter and viewholder classes and then you can inject your viewModel inside that viewholder class instead of the adapter.
To destroy the viewModel you should manually do it by observing the parentlifecycle. when the parent lifecycle event is ON_DESTROY do something similar to this in the init block of the adapter class.
parentLifecycle.addObserver(object : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onParentDestroy() {
recyclerView?.run {
for (i in 0 until childCount) {
getChildAt(i)?.let {
(getChildViewHolder(it) as BaseItemViewHolder<*, *>)
.run {
onDestroy()
viewModel.onManualCleared()
}
}
}
}
}
}
Here onManualCleared() function calls onCleared().

A view model shouldn't be injected inside an adapter, as I read in the comments you can you a better way than that,Let's imagine you have an adapter with many rows, each row when the user clicks on it, it performs a network call.
First, create an interface
interface Click {
fun onClick(index: Int, item: Model)
}
inside your adapter, init an instance of it then use it in your onBindViewHolder
yourview.setOnClickListener {v-> interface.onClick()}
don't forget to init the interface whether the place you're using it (Activity/Fragment/...).
This is a better solution than using a ViewModel for every row, which may lead to a SystemLeaks.

Related

Dagger dependency cycle between Presenter and Callback

I have a PresenterModule where I instantiate the presenter, passing as parameter a conteiner of callbacks. All these callbacks are instantiated, in CallbacksModule, passingas parameter the presenter, because need to communicate back to the presenters the results of the call.
The problem is that it is creating a dagger injection dependency cycle. How may I solve it? Thanks.
#Module
class CallbacksModule {
#Provides
fun provideProcessCallbacks(call1: RegistrationCallback,
call2: SignatureStartCallback,
call3: SignatureRegistrationCallback): GenerationProcessCallbacks {
return GenerationProcessCallbacks(call1, call2, call3)
}
#Provides
fun provideRegistrationCallback(presenter: StepPresenterImpl): RegistrationCallback {
return RegistrationCallback(presenter)
}
#Provides
fun provideSignatureStartCallback(presenter: StepPresenterImpl): SignatureStartCallback {
return SignatureStartCallback(presenter)
}
#Provides
fun provideSignatureRegistrationCallback(presenter: StepPresenterImpl): SignatureRegistrationCallback {
return SignatureRegistrationCallback(presenter)
}
}
presenter module:
#Module
class PresenterModule {
#Provides
fun provideStepProcessPresenter(callbacks: GenerationProcessCallbacks): StepPresenterImpl {
return StepPresenterImpl(callbacks)
}
callbacks code:
override fun onSuccess(registrationResponse: RegistrationRequestResponseModel?, useCaseIdentifier: String?) {
presenter?.onRegistered(registrationResponse)
}
override fun onUnexpectedError(t: Throwable?, useCaseIdentifier: String?) {
if (t != null && t is InvalidSessionException) {
return presenter?.showErrorOnView(t.code)
}
}
presenter code:
override fun onRegistered(registrationResponse: RegistrationRequestResponseModel?) {
signatureStartUseCase
.identifier(SignatureStartUseCase::class.simpleName)
.responseOnUI(false)
.execute(registrationResponse, callbacks.signatureStartCallback)
}
For the first two callbacks, they call another usecase. And for the last callback, it shows data on view.
As in the comments, you might not need the callbacks to be injected. Injection is typically pretty inexpensive, in terms of code and runtime cost, but it's not applicable for all cases. You may only want to inject in cases where it is useful to you to inject other dependencies or reconfigure the class through Dagger, though tests are a perfectly valid reason to want that reconfiguration.
The root of the dependency cycle problem, though, is that you are asking for a GenerationProcessCallbacks instance and Presenter instance at the same time, so Dagger can't create either one before the other. Instead, you can inject a Provider (Provider<GenerationProcessCallbacks>) or Lazy (Lazy<GenerationProcessCallbacks>), for which you only call get() outside the Presenter's constructor. This lets you create the Presenter first, because Dagger knows that you will have a Presenter instance with which to construct the GenerationProcessCallbacks. For that you'll also want to scope the component and Presenter with #Singleton (or some other scope annotation) so the same Presenter instance that creates the callbacks is also responsible for their generation; otherwise, Dagger will create an outer presenter that depends on the callbacks, and the callbacks will depend on one or more distinct Presenter instances compared to the outer one.

Should we use Koin Inject inside an Adapter? Or should we try to put this responsibility to the Activity/Fragment and its ViewModels?

Is it a good practice to do the following code?
Or should I remove these injected objects and try to handle all the things related to this adapter in its parent's ViewModel (creating a Listener between the adapter and its parent activity/fragment)?
class MyAdapter(private val listener: Listener) :
ListAdapter<MyEntity, RecyclerView.ViewHolder>(ItemDiffCallback()) {
// Getting instances using Koin Dependency Injection
private val dispatchers: CoroutineContextProvider
by inject(CoroutineContextProvider::class.java)
private val roomRepository: roomRepository
by inject(roomRepository::class.java)
...
}

View binding in traditional way using findViewbyId in LifecyclerObserver

I am new to the lifecycle observer (fragment). I am trying to link the views defined in XML with fragment. traditionally, we use to do it in onActivityCreated method using findViewById. How can we do it while using lifecycle observer?
Kindly do not suggest data binding. I am trying to avoid it in this scenario.
You can do it this way
class TestFragment : Fragment(), LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun doSomethingOnActivityCreated(){
requireActivity().lifecycle.removeObserver(this)
//do stuff
}
override fun onAttach(context: Context) {
super.onAttach(context)
requireActivity().lifecycle.addObserver(this)
}
}

Lifecycle scoping in a Fragment

I'm having a hard time understand what scopes to use for view models and live data when using fragments. Here is my ViewModel:
class MyViewModel: ViewModel() {
var myLiveData = MutableLiveData<WrappedResult<DataResponse>>()
private val repository = MyRespository()
private var job: Job? = null
fun getData(symbol: String) {
job = viewModelScope.launch(Dispatchers.IO) {
try {
val response = repository.getData(symbol)
withContext(Dispatchers.Main) {
myLiveData.value = WrappedResult.Success(response)
}
} catch(e: Exception) {
withContext(Dispatchers.Main) {
myLiveData.value = WrappedResult.Failure(e)
}
}
}
}
}
I can create the view model in the fragment using (where "this" is the fragment):
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
However, I can observe the LiveData with two options:
viewModel.getMyLiveData.observe(this...
or
viewModel.getMyLiveData.observe(getViewLifecycleOwner()...
It would appear that the job I create in the view model is going to be scoped to the fragment's lifecycle (through viewModelScope) and not the fragment's view lifecycle, but I have a choice between these two for the live data.
I could use some guidance and what the best practice is here. Also, does any of this matter whether the fragment has retained instance or not? Currently the fragment has setRetainInstance(true). Finally, from everything I've read I shouldn't need to clear the observer in the fragment or override onCleared when things are setup this way. Is that correct?
refer the doc of view model
https://developer.android.com/topic/libraries/architecture/viewmodel?gclid=Cj0KCQjwtZH7BRDzARIsAGjbK2blIS5rGzBxBdX6HpB5PMKgpUQHvdKXbwrt-ukTnWkpax1otMk4sm4aAuzPEALw_wcB&gclsrc=aw.ds#lifecycle
Viewmodel will only gets destoyed once the activity is finished.As the fragments are on the top of acitivity, the lifecycle of fragment will not affect the Viewmodel.The data will be persisted there on the viewmodel. So you can write a method to reset the data in viewmodel while you are entering in to oncreate of fragment.
In Fragment, OnCreate :
getViewModel.init()
on ViewModel
fun init() {
// clear all varialbes/datas/ etc here
}

Setting up LiveData observer in custom view without LifecycleOwner

I'm trying out the new Android Architecture components and have run into a road block when trying to use the MVVM model for a custom view.
Essentially I have created a custom view to encapsulate a common UI and it's respective logic to use throughout the app. I can set up the ViewModel in the custom view but then I'd have to either use observeForever() or manually set a LifecycleOwner in the custom view like below but neither seem correct.
Option 1) Using observeForever()
Activity
class MyActivity : AppCompatActivity() {
lateinit var myCustomView : CustomView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myCustomView = findViewById(R.id.custom_view)
myCustomView.onAttach()
}
override fun onStop() {
myCustomView.onDetach()
}
}
Custom View
class (context: Context, attrs: AttributeSet) : RelativeLayout(context,attrs){
private val viewModel = CustomViewModel()
fun onAttach() {
viewModel.state.observeForever{ myObserver }
}
fun onDetach() {
viewModel.state.removeObserver{ myObserver }
}
}
Option 2) Setting lifecycleOwner from Activity`
Activity
class MyActivity : AppCompatActivity() {
lateinit var myCustomView : CustomView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myCustomView = findViewById(R.id.custom_view)
myCustomView.setLifeCycleOwner(this)
}
}
Custom View
class (context: Context, attrs: AttributeSet) : RelativeLayout(context,attrs){
private val viewModel = CustomViewModel()
fun setLifecycleOwner(lifecycleOwner: LifecycleOwner) {
viewModel.state.observe(lifecycleOwner)
}
}
Am I just misusing the patterns and components? I feel like there should be a cleaner way to compose complex views from multiple sub-views without tying them to the Activity/Fragment
1 Option -
With good intention, you still have to do some manual work - like, calling onAttach\ onDetach Main purpose of Architecture components is to prevent doing this.
2 Option -
In my opinion is better, but I would say it's a bit wrong to bind your logic around ViewModel and View. I believe you can do same logic inside Activity/Fragment without passing ViewModel and LifecycleOwner to CustomView. Single method updateData is enough for this purpose.
So, in this particular case, I would say it's overuse of Architecture Components.
it doesn't make sense to manage the lifecycle of the the view manually by passing some reference of the activity to the views and calling onAttach/onDetach, when we already have the context provided when that view is created.
I have a fragment in a NavigationView that has other fragments in a view pager, more like a nested fragment hierarchy scenario.
I have some custom views in these top-level fragments, when the custom view is directly in the top fragment, I can get an observer like this
viewModel.itemLiveData.observe((context as ContextWrapper).baseContext as LifecycleOwner,
binding.item.text = "some text from view model"
}
when I have the custom view as a direct child of an activity I set it up directly as
viewModel.itemLiveData.observe(context as LifecycleOwner,
binding.item.text = "some text from view model"
}
in these activities, if I have a fragment and it has some custom view and I use the 2nd approach, I get a ClassCastException(), and I have to reuse these custom views in different places, both activities, and fragments (that's the idea of having a custom view)
so i wrote an extension function to set the LifeCycleOwner
fun Context.getLifecycleOwner(): LifecycleOwner {
return try {
this as LifecycleOwner
} catch (exception: ClassCastException) {
(this as ContextWrapper).baseContext as LifecycleOwner
}
}
now i simply set it everywhere as
viewModel.itemLiveData.observe(context.getLifecycleOwner(),
binding.item.text = "some text from view model"
}

Categories

Resources