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
Related
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.
Is there a way to inject instances of a Kotlin inline class with Dagger? In other words is there any way to get this code to compile assuming that we have a Dagger component that includes UserModule?
inline class Username(val name: String)
#Module
class UserModule {
#Provides
fun provideUsername(): Username = Username("default_user")
}
class MyClass #Inject constructor(private val username: Username)
I am specifically trying to do this on Android using Hilt. Not sure if that matters or not.
Same here - wanted this instead of injecting a "Named" or class instance wrapper string via Hilt:
#JvmInline
value class ApplicationFlavor #Inject constructor(private val flavor: String) {
fun isPaid() = flavor.equals("paid", true)
fun isFree() = flavor.equals("free", true)
fun trial() = flavor.equals("trail", true)
}
but decided to settle for using a class instance since I couldn't seem to find a workaround:
class ApplicationFlavor...
#Provides
#Singleton
fun provideApplicationFlavor() = ApplicationFlavor(BuildConfig.FLAVOR)
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.
I have the following definitions:
#Module
class WeaverDataModule {
// Provide the three pumps from providers
// All of them still explicitly mark 'Pump' as their return type
#Provides #IntoSet fun providesPump(thermosiphon: Thermosiphon) : Pump = thermosiphon
#Provides #IntoSet fun providesAnotherPump(suctionBased: SuctionBased) : Pump = suctionBased
#Provides #IntoSet fun providesGenericPump(genericPump: GenericPump) : Pump = genericPump
}
#Component(modules = [WeaverDataModule::class])
interface WeaverData {
// Get the CoffeeMaker
fun coffeeMaker(): CoffeeMaker
// Get the list of pumps
fun getPumps() : Set<Pump>
}
interface Pump
// The three pumps
class Thermosiphon #Inject constructor(val heater: Heater) : Pump
class SuctionBased #Inject constructor() : Pump
class GenericPump #Inject constructor() : Pump
// Some random heater
class Heater #Inject constructor()
In my code, when I do the following:
val cm = DaggerWeaverData.builder().build().getPumps()
I do get the three pumps as expected. However, when I'm trying to inject it into some other class:
class CoffeeMaker #Inject constructor(
private val heater: Heater,
private val pump: Set<Pump>
) {
fun makeCoffee() =
"Making coffee with heater ${heater::class.java} and using pumps" +
" ${pump.map { it::class.java }.joinToString(",")}"
}
I get the following error:
e: .../WeaverData.java:7: error: [Dagger/MissingBinding] java.util.Set<? extends weaver.Pump> cannot be provided without an #Provides-annotated method.
public abstract interface WeaverData {
^
java.util.Set<? extends weaver.Pump> is injected at
weaver.CoffeeMaker(…, pump)
weaver.CoffeeMaker is provided at
weaver.WeaverData.coffeeMaker()
I've tried injecting Collection<Pump> also, but I still get a similar error. In the dagger docs on multibinding, the example (in Java) shows the following:
class Bar {
#Inject Bar(Set<String> strings) {
assert strings.contains("ABC");
assert strings.contains("DEF");
assert strings.contains("GHI");
}
}
which is exactly what I'm doing. And for constructor-based injection, it is working just fine in Kotlin, because the following compiles and runs as expected:
class CoffeeMaker #Inject constructor(
private val heater: Heater
) {
fun makeCoffee() =
"Making coffee with heater ${heater::class.java}"
}
So I'm kind of at a loss on how do I get this multibinding to work.
So turns out what you need to do is:
class CoffeeMaker #Inject constructor(
private val heater: Heater,
private val pumps: Set<#JvmSuppressWildcards Pump>
) {
fun makeCoffee() =
"Making coffee with heater ${heater::class.java} with pumps ${pumps.map { it::class.java }.joinToString(",")}"
}
This is because Set is defined in Kotlin as Set<out E> which translates into Java as Set<? extends Pump>. From a type-theory perspective, Set<? extends Pump> is different from Set<Pump> and hence Dagger (probably) refuses to see Set<Pump> as an injectable for Set<? extends Pump>, which is fair and the right behavior.
The problem we have is that for any of these collections, since they are immutable by default, a declaration of type Set<X> will translate to Set<? extends X>, as an immutable collection only has references to the resolved type on returns and is hence covariant. To verify this theory, the following also works:
class CoffeeMaker #Inject constructor(
private val heater: Heater,
private val pumps: MutableSet<Pump>
) {
fun makeCoffee() =
"Making coffee with heater ${heater::class.java} with pumps ${pumps.map { it::class.java }.joinToString(",")}"
}
Note the use of MutableSet, which is defined as MutableSet<E> : Set<E> .... This is probably not something one should use because I doubt that this set is actually mutable. So what we do need is for the kotlin compiler to treat Set<out E> as Set<E> (the assignabiliy is valid in this case, just not the other way around). So do so, we use the #JvmSuppressWildcards annotation. I hope this helps somebody else facing similar issues.
I'm using kotlin android extensions to auto generate my Parcelables, but given the following code I'm getting 'Parcelable' should be a class.
the code:
sealed class Action : Parcelable
#Parcelize
object Run : Action()
#Parcelize
data class Ask(
val question: String
) : Action()
My understanding is that it is impossible to use #Parcelize in an object (Once it is working on the Ask class) in the way I'm doing it.
I want to use the Parcelable annotation in an object that extends a sealed class, so I'm do not know how to do it and do not write the following boilerplate.
object Run : Action() {
override fun writeToParcel(p0: Parcel?, p1: Int) {}
override fun describeContents() = 0
#JvmField
val CREATOR = object : Parcelable.Creator<Run> {
override fun createFromParcel(parcel: Parcel) = Run
override fun newArray(size: Int) = arrayOfNulls<Run?>(size)
}
}
You need to make sure that you have you Kotlin up to date.
You can follow an example here.