Provide instance without interface using Dagger - kotlin

There is way to provide instance without actual constructor calling.
class ModelImpl #Inject constructor(...): Model{}
#Provides
fun model(inst: ModelImpl): Model = inst
Is there way to do the same if there is no interface? Dagger knows already all dependencies for ModelImpl, so it can create an instance.
This gives obviously dependency cycle:
#Provides
fun model(inst: ModelImpl): ModelImpl = inst

When you use constructor injection Dagger can construct the object for you and you're already using Dagger to create ModelImpl to use it as a binding for Model in your example!
class ModelImpl #Inject constructor(...): Model{}
#Provides
fun model(inst: ModelImpl): Model = inst
// somewhere else...
// both variants would work!
#Inject lateinit var modelImpl : ModelImpl
#Inject lateinit var model : Model
The same would work without the interface
class ModelImpl #Inject constructor(...)
// somewhere else...
#Inject lateinit var model : ModelImpl
If you annotate the constructor then Dagger can create the object for you (if all dependencies can be resolved). This works the same wherever you request the object / dependency,
as a parameter in a #Provides annotated method (as your example)
as a field injection property (#Inject lateinit var)
as a parameter in another objects constructor
as a provision method in a component (fun getFoo() : Foo)
All of the following would work
// foo and bar can both be constructor injected
class Foo #Inject constructor()
class BarImpl #Inject constructor(val foo : Foo) : Bar
#Module
interface BarModule() {
#Binds // you should prefer Binds over Provides if you don't need initialization
// barImpl can be constructor injected, so it can be requested/bound to its interface here
fun bindBar(bar : BarImpl) : Bar
}
#Component(modules = BarModule::class)
interface BarComponent {
fun getBar() : Bar // returns barImpl due to binding
}
#Inject lateinit var bar : BarImpl // but we could as well use the implementation directly
#Inject lateinit var bar : Foo // or just foo
I recommend you to start with a small example, then compile the project and have a look at the generated code. If something is wrong you'll get errors immediately, while you can play around and try different setups!

An addition to David Medenjak anwser. If there is no interface and there is no requirement to group instances into modules, then module could be omitted completely:
class Model #Inject constructor(...){
//...
}
#Component
interface SomeComponent{
fun model(): Model
}
val model = someComponent.model()

Related

Why can't the ViewModel object be created automatically when I use Hilt as Dependency Injection in Jetpack Compose?

The following Code A is from the official sample project.
The project use Hilt as Dependency Injection.
In my mind, I needn't create the ViewModel object by myself because Hilt will create it automatically.
But in Code A, it seems that viewModel: MainViewModel = viewModel() must be created manually, why?
Code A
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun CraneHomeContent(
onExploreItemClicked: OnExploreItemClicked,
openDrawer: () -> Unit,
modifier: Modifier = Modifier,
viewModel: MainViewModel = viewModel(),
) {
...
}
#HiltViewModel
class MainViewModel #Inject constructor(
private val destinationsRepository: DestinationsRepository,
#DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
) : ViewModel() {
...
}
#HiltAndroidApp
class CraneApplication : Application()
By creating manually, I mean calling the view model constructor. And this is not practiced with Hilt, because you have to pass down all the injections.
You can't just declare the viewModel: MainViewModel parameter, because then you have to pass it from the calling view.
The viewModel() does all the injection magic for you. Also, if the view model already exists, the same object will be returned. So it's completely automatic.

Where is the defaultDispatche instanced by Hilt?

The Code A is from the end branch of the official sample project.
The project use Hilt to implement dependency injection.
I searched the the whole project, I find only the code annotation class DefaultDispatcher, but I can't understand what the code mean.
Where is the defaultDispatche instanced by Hilt ?
Code A
#HiltViewModel
class MainViewModel #Inject constructor(
private val destinationsRepository: DestinationsRepository,
#DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
) : ViewModel() {
...
}
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun CraneHomeContent(
onExploreItemClicked: OnExploreItemClicked,
openDrawer: () -> Unit,
modifier: Modifier = Modifier,
viewModel: MainViewModel = viewModel(),
) {
...
}
#Retention(AnnotationRetention.BINARY)
#Qualifier
annotation class DefaultDispatcher
You should have a module declared like this
#Module
object DispatcherModule {
#DefaultDispatcher
#Provides
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
}
Now you create this class in order to use #DefaultDispatcher as an annotation
#Retention(AnnotationRetention.BINARY)
#Qualifier
annotation class DefaultDispatcher
For more details, visit here
In here on your sample project, that you have shared is perfectly written like this.
This means #DefaultDispatcher will be used as an annotation, where value will be provided by the DispatcherModule

String cannot be provided without an #Provides-annotated method

I'm trying to use ActivityScoped and ActivityComponent in a simple project, but i was getting below error
#javax.inject.Named("String2") java.lang.String cannot be provided without an
#Provides-annotated method.
public abstract static class SingletonC implements BaseApplication_GeneratedInjector,
#javax.inject.Named("String2") java.lang.String is injected at
com.example.hiltoplayground.TestViewModel(testString)
com.example.hiltoplayground.TestViewModel is injected at
com.example.hiltoplayground.TestViewModel_HiltModules.BindsModule.binds(vm)
#dagger.hilt.android.internal.lifecycle.HiltViewModelMap
java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>>
is requested at
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com.example.hiltoplayground.BaseApplication_HiltComponents.SingletonC → com.example.hiltoplayground.BaseApplication_HiltComponents.ActivityRetainedC → com.example.hiltoplayground.BaseApplication_HiltComponents.ViewModelC]
But if i use SingletonComponent and #Singleton it runs perfect, no issue.
Here is the affected module
#Module
#InstallIn(ActivityComponent::class)
object MainModule {
#ActivityScoped
#Provides
#Named("String2")
fun provideTestString2 (
#ApplicationContext context: Context
) = context.getString(R.string.string_inject)
}
Below is the ViewModel (No problem here i guess)
#HiltViewModel
class TestViewModel #Inject constructor(
#Named("String2") testString: String): ViewModel() {
init{
Log.d("String2Message","Show $testString")
}
}
You can use #ActivityRetainedScoped in place of #ActivityScoped and make #InstallIn(ActivityRetainedComponent::class) from #InstallIn(ActivityComponent::class).

Can I use #Inject lateinit var navigator: AppNavigatorImpl instead of #Binds abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator?

I'm learning dependency injection by the article with the project。
The Code A need instance an object navigator by dependency injection , so the author use Code B to implement it, you can see it here.
I'm very strange that the class AppNavigatorImpl has implemented the class AppNavigator by dependency injection in Code C, so I think Code D will work well.
Can I use #Inject lateinit var navigator: AppNavigatorImpl instead of #Binds abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator ?
Code A
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject lateinit var navigator: AppNavigator
...
}
Code B
#InstallIn(ActivityComponent::class)
#Module
abstract class NavigationModule {
#Binds
abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator
}
Code C
class AppNavigatorImpl #Inject constructor(private val activity: FragmentActivity) : AppNavigator {
override fun navigateTo(screen: Screens) {
val fragment = when (screen) {
Screens.BUTTONS -> ButtonsFragment()
Screens.LOGS -> LogsFragment()
}
activity.supportFragmentManager.beginTransaction()
.replace(R.id.main_container, fragment)
.addToBackStack(fragment::class.java.canonicalName)
.commit()
}
}
Code D
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject lateinit var navigator: AppNavigatorImpl
...
}
Technically, you can.
The question is, whether you actually want to do that.
Usually you want to depend on abstractions, not concretions (dependency inversion principle).
If you'd injected the AppNavigatorImpl, then you depend on concretion instead of it's abstraction AppNavigator.
Also, that page in the codelab teaches how to inject an interface (or an abstract class) as those can't have constructor annotated with #Inject.
In your codebase, you usually need to decide whether it's necessary to have this abstraction or not.

Kotlin Dagger 2 Sharing ViewModel between Activity and Fragment

As mentioned in the title I've got a problem with Dagger 2 injection. I have a single activity and multiple fragments. I'm trying to share activity's ViewModel with every child fragment. I based my solution on Google's Android Architecture ToDo sample. I've created ViewModelFactory as
ViewModelFactory
If you jump to the link you will see that in their solution there's a separate ViewModel for every activity and fragment. They are not showing how to deal with scenarios like mine. My implementation of ActivityModule looks like:
#Module
abstract class SampleModule {
#ContributesAndroidInjector(
modules = [
ViewModelBuilder::class
]
)
internal abstract fun sampleActivity(): SampleActivity
#Binds
#IntoMap
#ViewModelKey(SampleViewModel::class)
abstract fun bindViewModel(viewModel: SampleViewModel): ViewModel
}
My activity extends DaggerAppCompatActivity and fragment DaggerFragment and as follows my injection of view model looks simple as
class SampleActivity : DaggerAppCompatActivity() {
#Inject
lateinit var viewModel: SampleViewModel
...
I can't find a web solution to my problem. I'm a pretty novice user of Dagger. I've tried to implement Subcomponent but still, it's not working because all the examples I have searched so far didn't use DaggerApplication, DaggerAppCompatActivity and my way of injection. Please suggest any solution or if a subcomponent way is right please show me how to do it if it's possible in my current architecture.
Thank you very much in advance.
#silaros88 I was facing yor same issue, share a ViewModel between multiple fragmnets in a single Activity application, and i solved playing with the ViewmModelStoreOwner.
Steps to fix your issue.
See here TasksFragment.kt how they inject the ViewModelProvider.Factory instead of the ViewModel
Retrieve the desire ViewModel using one of this two options:
viewModels<SharedDesireViewModel> (requireActivity()) { Injected ViewModelProvider.Factory }
ViewModelProvider(requireActivity(), Injected ViewModelProvider.Factory ).get(SharedDesireViewModel::class.java)
Examples:
Option #1:
FragmentA.kt
class FragmentA: DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelProvider.Factory
private val mainViewModel: MainViewModel by viewModels({requireActivity()}) { viewModelProviderFactory }
......
FragmentB.kt
class FragmentB: DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelProvider.Factory
private val mainViewModel: MainViewModel by viewModels({requireActivity()}) { viewModelProviderFactory }
......
Option #2:
FragmentA.kt
class FragmentA: DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelProvider.Factory
private val mainViewModel: MainViewModel by lazy {
ViewModelProvider(requireActivity(), viewModelProviderFactory)
.get(MainViewModel::class.java)
}
......
FragmentB.kt
class FragmentB: DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelProvider.Factory
private val mainViewModel: MainViewModel by lazy {
ViewModelProvider(requireActivity(), viewModelProviderFactory)
.get(MainViewModel::class.java)
}
......
I solved the problem with a slightly different approach.
Since the fragments and the activity both have dagger modules.
In the ActivityModule I am providing the sharedViemodel as below
#Module
class ActivityModule(private val activity: AppCompatActivity)
{
#Provides
fun provideMainSharedVieModel() : MainSharedViewModel =
ViewModelProvider(activity).get(MainSharedViewModel::class.java)
}
And in my fragment module I am again providing the same viemodel as below:
#Module
class FragmentModule(private val fragment: Fragment)
{
#Provides
fun provideMainSharedVieModel() : MainSharedViewModel =
ViewModelProvider(fragment.activity!!).get(MainSharedViewModel::class.java)
}
Since the ViewModels are stored in a map with the activity or fragments as the key, hence providing the sharedViewModel with "fragment.activity!!" in the Fragment module will not create a new instance of the viewmodel , it will just the return the already instantiated shared viewmodel to the fragment.