Closed rakshakhegde closed 7 years ago
There is both a philosophical point and logistical/implementation point to be made here.
First, it's not fully clear to us that injecting views is the right thing to do. View
objects are meant to draw, and not much else. The controller (in a traditional MVC pattern) is the one which can coordinate and pass around the appropriate data to a view. Injecting a view blurs the lines between fragments and views (perhaps a child fragment is really the appropriate construct instead?)
From the implementation perspective, there is also a problem in that there isn't a canonical way to retrieve the View
's parent Fragment
s (if any), or Activity
to retrieve a parent component. There have been hacks suggested to build in that relationship, but so far we haven't seen anything that seems to suggest that we could do this correctly. We could just call View.getContext().getApplicationContext()
and inject from there, but skipping the intermediate layers without any option for something in between is inconsistent with the rest of our design, and probably confusing to users even if it works.
it might be possible to use https://github.com/InflationX/ViewPump in a way that allows you to use normal constructor injection for custom views as well, thus making special constructs for injecting a view unneeded at least in the context of dagger-android.
it's not fully clear to us that injecting views is the right thing to do
while I would like to agree, fragments have their own issues, which has let to the rise of using custom views
Custom views are not a replacement for fragments. You still need a coordination layer which is responsible for inflating, transitioning, and managing the stack. It should be that component's responsibility for injecting any views (or at the very least, providing the injector via the context hack).
Custom views are not a replacement for fragments.
agreed, I was just pointing out the reason for the use case
I currently inject Square's Coordinators via constructor and and I bind the coordinators to the custom views from inside the Activity. So the Activity becomes just a container for custom views and a coordination layer. Any opinions - is that correct?
Would anyone elaborate the context hack approach?
Not directly related, but discussed this a bit with @digitalbuddha here (and here's the gist)
I'm open to being told this is bad design, but here's my situation and why I'd like the ability to easily inject View
s.
My app's monetization strategy is based around subscriptions, and we have three tiers of membership -- call them Gold, Bronze, Silver.
I have a custom view, TierLayout
, and a custom style attribute,
<declare-styleable name="TierLayout">
<!-- Gold | Silver | Bronze -->
<attr name="tier" format="string"/>
</declare-styleable>
I can set the tier on a TierLayout
in XML.
In my custom view, I take a peak at the AttributeSet
to see what the XML says and set the appropriate tier and set all the correct values relating to that programmatically. Of course, a Tier
is a complex beast, so I get one via a TierFactory
, which has an @Inject
constructor.
In the current prod version of my app, I have a static reference to the relevant @Subcomponent
, and I can call, e.g. DaggerUtil.INSTANCE.getUpgradeSubcomponent().inject(this /* TierLayout */)
. This gives me my TierFactory
, from which I can call tierFactory.fromString(tierName /* from attrs */)
. I can do this right in the view's constructor.
I am in the fortunate (?) position of rewriting my app from scratch, and I've adopted dagger.android
as the approach. This means I no longer have an easy static reference to that subcomponent, since I let the lib instantiate it for me.
@ContributesAndroidInjector abstract fun upgradeActivity(): UpgradeActivity
Please note, the existing prod code was written in Java, works well, and I'm basically just trying to port it over as-is to the new app (and in Kotlin, but that's an aside).
What is the recommended approach for getting an injectable object into my custom view?
Time for us to just wait for Jake Wharton to solve this problem for us in https://github.com/square/AssistedInject 's inflation-inject
😛
Any news on this?
You can inject anything you want with Dagger-Android as long as you create the dagger injection module just like AndroidInjectionModule.class for your target iirc
I am a bit new with dagger, I searched a lot for an example of a custom view with injection but no luck. It would be great if you could refer me to an example or article for injection in a custom view.
@ghahramani if you check the source code, AndroidInjectionModule.class
looks like this
@Module
public abstract class AndroidInjectionModule {
@Multibinds
abstract Map<Class<?>, AndroidInjector.Factory<?>> classKeyedInjectorFactories();
@Multibinds
abstract Map<String, AndroidInjector.Factory<?>> stringKeyedInjectorFactories();
private AndroidInjectionModule() {}
}
So what you need to do is get a Class<?>, AndroidInjector.Factory<?>
in for a given custom view.
It might even work just by providing @ContributesAndroidInjector
for a custom view.
If not, this factory looks like this:
@Module(
subcomponents =
MySubcomponent.class
)
public abstract class MyFragmentModule {
private MyFragmentModule() {}
@Binds
@IntoMap
@ClassKey(MyFragment.class)
abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory(
MyFragmentSubcomponent.Factory builder);
@Subcomponent
@PerScreen
public interface MyFragmentSubcomponent
extends AndroidInjector<MyFragment> {
@Subcomponent.Factory
interface Factory extends AndroidInjector.Factory<MyFragment> {}
}
}
So if @ContributesAndroidInjector
didn't work, you need to replace MyFragment
with MyView
and it would theoretically work.
You can check https://stackoverflow.com/questions/53889327/dagger-android-for-custom-class-possible which worked with Conductor Controllers, the idea should be the same.
I hope you won't need to worry about DispatchingAndroidInjector
, I'm not sure when that comes into the picture.
Wow, @ContributesAndroidInjector
works. Thank you so much. I was literally looking for this around a week. I found out about Mortar from Square guys, what do you think about it? It works on top of Dagger2 https://github.com/square/mortar
Wow, @ContributesAndroidInjector works. Thank you so much.
That's pretty cool, I guess the 2.20 update made the library much more powerful.
Mortar works independently of Dagger2, its development has been dead for 3 years, and I was happy to rip the BundleServiceRunner
out of our code and replace it with simple-stack a while ago.
Mortar itself is not based on Dagger2 at all btw, it holds Map<String, Any>
in a Map<String, Map<String, Any>>
where the String
keys identify a scope
, and in that scope
they stored a Dagger component in some samples. The only trick is that this map is in Application, so it doesn't die with the Activity.
Great. Thank you for the explanation. In that case I won't go with it as you mentioned it is a dead project (3 years no development :-1: )
@ghahramani , @Zhuinden , sorry but I am not quite following how you got the @ContributesAndroidInjector to work with a custom view, is there a code example you can point me at please?
@CamiloVega I'll post the example tomorrow, Also I'm almost finishing a medium related to that.
@CamiloVega Here is an example of it
The Module:
@Module
internal abstract class CustomViewDiModule {
@ContributesAndroidInjector
abstract fun bindDictionaryTextView(): DictionaryTextView
}
And here is the DictionaryTextView
class DictionaryTextView : XXXText {
@Inject
protected lateinit var viewModel: XXXViewModel
init {
application.androidInjector().inject(this)
}
@Suppress("USELESS_CAST")
override val application: DaggerApplication by lazy {
val ctx = context.applicationContext
if (ctx is DaggerApplication) {
return@lazy ctx as DaggerApplication
}
throw IllegalStateException("Application context does not extend DaggerApplication: $context")
}
}
Now XXXViewModel
is injected
Hope it helps, let me know if you need any help
@ghahramani I have no way to thank you, this has saved me so much work and time, and now my code definitely looks cleaner. Thank you!
@CamiloVega Glad that could help, I have finished the medium post, please check it for further information
We have inject for Activity, Fragment, Receivers, etc., but one is required for custom views too.