Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AndroidInjection.inject(View) #720

Closed
rakshakhegde opened this issue May 1, 2017 · 22 comments
Closed

Add AndroidInjection.inject(View) #720

rakshakhegde opened this issue May 1, 2017 · 22 comments

Comments

@rakshakhegde
Copy link

We have inject for Activity, Fragment, Receivers, etc., but one is required for custom views too.

@ronshapiro
Copy link

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 Fragments (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.

@trevjonez
Copy link

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.

@ZakTaccardi
Copy link

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

@JakeWharton
Copy link

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).

@ZakTaccardi
Copy link

Custom views are not a replacement for fragments.

agreed, I was just pointing out the reason for the use case

@sevar83
Copy link

sevar83 commented May 1, 2017

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?

@jemshit
Copy link

jemshit commented Nov 14, 2017

Would anyone elaborate the context hack approach?

@ronshapiro
Copy link

Not directly related, but discussed this a bit with @digitalbuddha here (and here's the gist)

@autonomousapps
Copy link

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 Views.

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?

@Zhuinden
Copy link

Time for us to just wait for Jake Wharton to solve this problem for us in https://github.com/square/AssistedInject 's inflation-inject 😛

@ghahramani
Copy link

Any news on this?

@Zhuinden
Copy link

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

@ghahramani
Copy link

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.

@Zhuinden
Copy link

@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.

@ghahramani
Copy link

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

@Zhuinden
Copy link

Zhuinden commented Dec 18, 2019

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.

@ghahramani
Copy link

ghahramani commented Dec 19, 2019

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 👎 )

@CamiloVega
Copy link

@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?

@ghahramani
Copy link

@CamiloVega I'll post the example tomorrow, Also I'm almost finishing a medium related to that.

@ghahramani
Copy link

ghahramani commented Feb 4, 2020

@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

@CamiloVega
Copy link

@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!

@ghahramani
Copy link

ghahramani commented Mar 27, 2020

@CamiloVega Glad that could help, I have finished the medium post, please check it for further information

https://medium.com/@ghahremani/android-custom-view-lifecycle-with-dependency-injection-as-a-bonus-4a55217e15d8?sk=b62089ab35a5d0d0f379e194bbd2ae30

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests