Dagger Hilt: Inject interface that inherits another interface - android

I have an app that implements MVP pattern and I want to simplify boilerplate code with Hilt. So far so good. The problem comes when I want to Inject a presenter that takes as a paramter a interface implementation of MyView, but I want to pass a implementation of an interface that inherits MyView because the presenter may be injected in diferent fragments with diferent MyView implementations
My presenter:
class BluetoothPresenter #Inject constructor(
#ApplicationContext private val context: Context,
private val view: MyView
) {
The presenter is injected in the Fragment:
class DevicesListFragment() : Fragment(), DevicesListMyView {
#Inject
lateinit var btPresenter: BluetoothPresenter
DevicesListView is a interface that inherits MyView interface
interface DevicesListView : MyView {
fun onSearchDevicesStarted();
fun searchDevicesFinished();
fun onDeviceFound(device: BluetoothDevice)
}
MyView:
interface MyView {
fun showError()
fun showMessage()
}
My module that tells Hilt how to provide the MyView:
#Module
#InstallIn(SingletonComponent::class)
interface MainModule {
#Binds
fun provideView(devicesListFragment: DevicesListView) : MyView
#Binds
fun provideDeviceView(devicesListFragment: DevicesListFragment) : DevicesListView
}
I get this error:
kotlin.UninitializedPropertyAccessException: lateinit property btPresenter has not been initialized
Thanks

Related

Get all instances of an interface with Dagger Hilt

I'm migrating DI from Koin to Dagger Hilt. I have a custom interface with many implementations, and I want to inject all the instances in a useCase as a list.
For example:
#Singleton
class MyUseCaseImpl #Inject constructor(
private val myInterfaces: List<MyInterface>,
) : MyUseCase {
...
}
When I used Koin, I would do:
single<MyUseCase> {
MyUseCaseImpl(
myInterfaces = getKoin().getAll(),
)
}
How can I do the same with Hilt?
I've already binded each implementation with my interface like:
#Binds
abstract fun bindFirstMyInterfaceImpl(
firstMyInterfaceImpl: FirstMyInterfaceImpl,
): MyInterface
You need multibindings. Provide your dependencies with #IntoSet annotation
#Binds
#IntoSet
abstract fun bindFirstMyInterfaceImpl(
impl: FirstMyInterfaceImpl1,
): MyInterface
#Binds
#IntoSet
abstract fun bindSecondMyInterfaceImpl(
impl: SecondMyInterfaceImpl,
): MyInterface
But instead of List multibindings uses Set (or Map)
#Singleton
class MyUseCaseImpl #Inject constructor(
private val myInterfaces: Set<#JvmSuppressWildcards MyInterface>,
) : MyUseCase {
...
}

How to inject adapter with hilt in fragment?

If Adapter have an interface like clickListener, Fragment implement that interface and Fragment pass the instance of interface in constructor to the adapter, How to inject adapter with hilt?
How to solve this issue?
Here is the error
error: [Dagger/MissingBinding] ... cannot be provided without an #Inject constructor or an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
#AndroidEntryPoint
class HomeFragment: Fragment(), ShowsAdapter.Interaction {
#Inject
lateinit var adapter: ShowsAdapter
private val viewModel: HomeViewModel by hiltNavGraphViewModels(R.id.my_nav)
....
}
class ShowsAdapter #Inject constructor(private val interaction: Interaction) :
RecyclerView.Adapter<ShowsAdapter.ShowsHolder>() {
....
interface Interaction {
fun onItemSelected(show: Show)
}
}
#Module
#InstallIn(FragmentComponent::class)
abstract class HomeModule {
#Binds
abstract fun provideInteraction(homeFragment: HomeFragment): ShowsAdapter.Interaction
}

Use the same instance of view model in multiple fragments using dagger2

I am using only dagger2 (not dagger-android) in my project. It's working fine to inject the ViewModel using multibinding. But there's one problem with that previously without dagger2 I was using the same instance of viewmodel used in activity in multiple fragments (using fragment-ktx method activityViewModels()), but now since dagger2 is injecting the view model it's always gives the new instance (checked with hashCode in each fragment) of the viewmodel for each fragment, that's just breaks the communication between fragment using viewmodel.
The fragment & viewmodel code is as below:
class MyFragment: Fragment() {
#Inject lateinit var chartViewModel: ChartViewModel
override fun onAttach(context: Context) {
super.onAttach(context)
(activity?.application as MyApp).appComponent.inject(this)
}
}
//-----ChartViewModel class-----
class ChartViewModel #Inject constructor(private val repository: ChartRepository) : BaseViewModel() {
//live data code...
}
Here's the code for viewmodel dependency injection:
//-----ViewModelKey class-----
#MapKey
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
//-----ViewModelFactory class------
#Singleton
#Suppress("UNCHECKED_CAST")
class ViewModelFactory
#Inject constructor(
private val viewModelMap: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = viewModelMap[modelClass] ?: viewModelMap.asIterable()
.firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
?: throw IllegalArgumentException("Unknown ViewModel class $modelClass")
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
//-----ViewModelModule class-----
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(ChartViewModel::class)
abstract fun bindChartViewModel(chartViewModel: ChartViewModel): ViewModel
}
Is there any way to achieve the same instance of viewmodel for multiple fragment and also at the same time inject the view model in fragments.
Also is there any need for the bindViewModelFactory method as it seems to have no effect on app even without this method.
One workaround could be to make a BaseFragment for fragments which shares the common viewmodel, but that will again include the boilerplate code and also I am not a great fan of BaseFragment/BaseActivity.
This is generated code for ChartViewModel which always create the newInstance of viewModel:
#SuppressWarnings({
"unchecked",
"rawtypes"
})
public final class ChartViewModel_Factory implements Factory<ChartViewModel> {
private final Provider<ChartRepository> repositoryProvider;
public ChartViewModel_Factory(Provider<ChartRepository> repositoryProvider) {
this.repositoryProvider = repositoryProvider;
}
#Override
public ChartViewModel get() {
return newInstance(repositoryProvider.get());
}
public static ChartViewModel_Factory create(Provider<ChartRepository> repositoryProvider) {
return new ChartViewModel_Factory(repositoryProvider);
}
public static ChartViewModel newInstance(ChartRepository repository) {
return new ChartViewModel(repository);
}
}
The problem is that when you inject the viewmodel like this
class MyFragment: Fragment() {
#Inject lateinit var chartViewModel: ChartViewModel
dagger simply creates a new viewmodel instance. There is no viewmodel-fragment-lifecycle magic going on because this viewmodel is not in the viewmodelstore of the activity/fragment and is not being provided by the viewmodelfactory you created. Here, you can think of the viewmodel as any normal class really. As an example:
class MyFragment: Fragment() {
#Inject lateinit var anything: AnyClass
}
class AnyClass #Inject constructor(private val repository: ChartRepository) {
//live data code...
}
Your viewmodel is equivalent to this AnyClass because the viewmodel is not in the viewmodelstore and not scoped to the lifecycle of the fragment/activity.
Is there any way to achieve the same instance of viewmodel for multiple fragment and also at the same time inject the view model in fragments
No. Because of the reasons listed above.
Also is there any need for the bindViewModelFactory method as it seems to have no effect on app even without this method.
It does not have any effect because (I'm assuming that) you are not using the ViewModelFactory anywhere. Since it's not referenced anywhere, this dagger code for the viewmodelfactory is useless.
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
Here's what #binds is doing: 1 2
That's why removing it has no effect on the app.
So what is the solution? You need to inject the factory into the fragment/activity and get the instance of the viewmodel using the factory
class MyFragment: Fragment() {
#Inject lateinit var viewModelFactory: ViewModelFactory
private val vm: ChartViewModel by lazy {
ViewModelProvider(X, YourViewModelFactory).get(ChartViewModel::class.java)
}
What is X here? X is ViewModelStoreOwner. A ViewModelStoreOwner is something that has viewmodels under them. ViewModelStoreOwner is implemented by activity and fragment. So you have a few ways of creating a viewmodel:
viewmodel in activity
ViewModelProvider(this, YourViewModelFactory)
viewmodel in fragment
ViewModelProvider(this, YourViewModelFactory)
viewmodel in fragment (B) scoped to a parent fragment (A) and shared across child fragments under A
ViewModelProvider(requireParentFragment(), YourViewModelFactory)
viewmodel in fragment scoped to parent activity and shared across fragments under the activity
ViewModelProvider(requireActivity(), YourViewModelFactory)
One workaround could be to make a BaseFragment for fragments which shares the common viewmodel, but that will again include the boilerplate code and also I am not a great fan of BaseFragment/BaseActivity
Yes, this is indeed a bad idea. The solution is to use requireParentFragment() and requireActivity() to get the viewmodel instance. But you'll be writing the same in every fragment/activity that has a viewmodel. To avoid that you can abstract away this ViewModelProvider(x, factory) part in a base fragment/activity class and also inject the factory in the base classes, which will simplify your child fragment/activity code like this:
class MyFragment: BaseFragment() {
private val vm: ChartViewModel by bindViewModel() // or bindParentFragmentViewModel() or bindActivityViewModel()
You can share ViewModel between fragments when instantiating if the fragments has the same parent activity
FragmentOne
class FragmentOne: Fragment() {
private lateinit var viewmodel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewmodel= activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} : throw Exception("Invalid Activity")
}
}
FragmentTwo
class FragmentTwo: Fragment() {
private lateinit var viewmodel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewmodel= activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
}
}
Add your ViewModel as PostListViewModel inside ViewModelModule:
#Singleton
class ViewModelFactory #Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T = viewModels[modelClass]?.get() as T
}
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(PostListViewModel::class)
internal abstract fun postListViewModel(viewModel: PostListViewModel): ViewModel
//Add more ViewModels here
}
To end with, our activity will have ViewModelProvider.Factory injected and it will be passed to theprivate val viewModel: PostListViewModel by viewModels { viewModelFactory }
class PostListActivity : AppCompatActivity() {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private val viewModel: PostListViewModel by viewModels { viewModelFactory }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_post_list)
getAppInjector().inject(this)
viewModel.posts.observe(this, Observer(::updatePosts))
}
//...
}
For more check this post:Inject ViewModel with Dagger2 And Check github

How to inject a LifecycleOwner in Android using Dagger2?

I happen to have an Android lifecycle aware component with the following interface:
class MyLifecycleAwareComponent #Inject constructor(
private val: DependencyOne,
private val: DependencyTwo
) {
fun bindToLifecycleOwner(lifecycleOwner: LifecycleOwner) {
...
}
...
}
All Dagger specific components and modules are configured correctly and have been working great so far.
In each activity when I need to use the component I do the following:
class MyActivity: AppCompatActivity() {
#Inject
lateinit var component: MyLifecycleAwareComponent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
component.bindToLifecycleOwner(this)
...
}
}
Now I want to get rid of bindLifecycleOwner and denote my component like this:
class MyLifecycleAwareComponent #Inject constructor(
private val: DependencyOne,
private val: DependencyTwo,
private val: LifecycleOwner
) {
...
}
And provide the lifecycleOwner within the scope of individual activities (which implement the interface by extending AppCompatActivity).
Is there any way to do it with Dagger?
You may bind your Activity to LifecycleOwner from your ActivityModule:
#Module
abstract class ActivityModule {
...
#Binds
#ActivityScope
abstract fun bindLifecycleOwner(activity: AppCompatActivity): LifecycleOwner
...
}

Dagger2 singleton not actually a singleton issue

I have an object that I'm trying to inject into 3 fragments as a singleton, using #Inject annotations.
Component:
#Subcomponent(modules = [(MyModule::class)])
interface MyComponent {
fun inject(fragment: OneFragment)
fun inject(fragment: TwoFragment)
fun inject(fragment: ThreeFragment)
}
Module:
#Module
class MyModule(val view: MyView) {
#Provides
fun provideView(): MyView = view
#Provides
fun providePresenter(view: MyView,
myService: MyService): MyPresenter =
MyPresenterImpl(view, myService)
}
MyPresenterImpl:
#Singleton
class MyPresenterImpl(override val view: MyView,
override val myService: MyService): MyPresenter {
private val TAG = javaClass.simpleName
// other code (irrelevant)
}
Fragments:
class OneFragment : Fragment() {
#Inject
lateinit var myPresenter: MyPresenter
override fun onCreateView(inflater: LayoutInflater, viewGroup: ViewGroup?,
bundle: Bundle?): View? {
activity?.mainApplication?.appComponent?.plus(
MyModule(activity as MyActivity))?.inject(this)
val view = inflater.inflate(R.layout.fragment_one, viewGroup, false)
//other meaningless code
return view
}
}
However, the presenters that are injected into the fragments are not the same unique instance. What am I doing wrong?
It's not a #Singleton because you did not tell Dagger that it is. You put the scope on the presenter which would be fine if you were using constructor injection, but you aren't.
#Singleton // does nothing. you're not doing constructor injection.
class MyPresenterImpl(override val view: MyView,
override val myService: MyService): MyPresenter
// ...and in your module...
#Provides // where's the scope? it's unscoped.
fun providePresenter(view: MyView,
myService: MyService): MyPresenter =
MyPresenterImpl(view, myService)
So you have two options. Either you use constructor injection, or you use a #Provides. You can't cherry pick and use a bit of each one.
1. Constructor injection
Just remove the #Provides annotated method in your module and slap an #Inject on your constructor.
#Singleton // NOW we're doing constructor injection -> singleton!
class MyPresenterImpl #Inject constructor(override val view: MyView,
override val myService: MyService): MyPresenter
There is no need for a module declaring it now. If you want to bind it to an interface you can use the #Binds annotation and continue using constructor injection...
#Binds // bind implementation to interface
abstract fun providePresenter(presenter : MyPresenterImpl) : MyPresenter
Since MyPresenterImpl is a #Singleton you don't have to declare MyPresenter as a singleton also. It will always use the singleton under the hood. (I believe it might even have a slight bigger performance penalty doing scopes on both.)
2. #Provides method
Remove the scope from your class (where it does nothing but confuse), and put it on the #Provides method instead. That's it.
#Singleton // singleton!
#Provides
fun providePresenter(view: MyView,
myService: MyService): MyPresenter =
MyPresenterImpl(view, myService)
Scope on the class for constructor injection, or scope on the #Provides method if you're using that. Don't mix them up.
I'd personally recommend to go for constructor injetion whenever possible to do so as you don't have to manage/update the constructor calls yourself.

Categories

Resources