Skip to content

Instantly share code, notes, and snippets.

@ronshapiro
Last active April 24, 2024 00:19
Show Gist options
  • Save ronshapiro/af99799fe9dd7ec412f2d4e45b843370 to your computer and use it in GitHub Desktop.
Save ronshapiro/af99799fe9dd7ec412f2d4e45b843370 to your computer and use it in GitHub Desktop.
dagger.android for views in ~10 minutes

1. You can implement View.getActivity() like this:

public interface ViewWithActivity {
  // Using android-gradle-plugin 3.0, which has the desugar step for default methods on interfaces
  default Activity getActivity() {
    // https://android.googlesource.com/platform/frameworks/support/+/03e0f3daf3c97ee95cd78b2f07bc9c1be05d43db/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java#276
    Context context = getContext();
    while (context instanceof ContextWrapper) {
      if (context instanceof Activity) {
        return (Activity)context;
      }
      context = ((ContextWrapper) context).getBaseContext();
    }
    throw new IllegalStateException("Context does not stem from an activity: " + getContext());
  }
}

2. Create a parallel to the Has*Injector classes

interface HasViewInjector {
  AndroidInjector<View> viewInjector();
}

3. Define a new activity base class:

public abstract DaggerAppCompatWithViewActivityNamingIsFun
    extends DaggerAppCompatActivity
    implements HasViewInjector {
  @Inject DispatchingAndroidInjector<View> viewInjector;
  
  @Override
  public AndroidInjector<View> viewInjector() {
    return viewInjector;
  }
}

4. Create a module

@Module
interface ViewInjectionModule {
  @Multibinds
  abstract Map<Class<? extends View>, AndroidInjector.Factory<? extends View>>
      viewInjectorFactories();
}

// ...

@Component(
  modules = {
    AndroidSupportInjectionModule.class,
    ViewInjectionModule.class, 
    // ...
  })
interface AppComponent

5. ViewInjection.inject

public final class ViewInjection {
  public static void inject(ViewWithActivity view) {
   checkNotNull(view, "view");
    Activity activity = view.getActivity();
    if (!(application instanceof HasViewInjector)) {
      throw new RuntimeException(
          String.format(
              "%s does not implement %s",
              activity.getClass().getCanonicalName(),
              HasViewInjector.class.getCanonicalName()));
    }

    AndroidInjector<View> viewInjector =
        ((HasViewInjector) activity).viewInjector();
    checkNotNull(viewInjector, "%s.viewInjector() returned null", activity.getClass());

    viewInjector.inject(view);
  }
}

6. Equivalent of @ContributesAndroidInjector MyView

7. Call ViewInjection.inject(this)

I'm not sure where this should happen yet, perhaps onFinishInflate or onAttachedToWindow()?

@jkaan
Copy link

jkaan commented Sep 12, 2019

@hijamoya This actually broke for me after upgrading to Dagger 2.20, in 2.19 it still works. If you want to fix it you have to do two changes:

  1. Change @ViewKey for @ClassKey.
  2. Change the return type of the AndroidInjector.Factory to AndroidInjector.Factory

@ronshapiro
Copy link
Author

ronshapiro commented Sep 12, 2019 via email

@davidliu
Copy link

In recent dagger releases, a lot of this has been made deprecated I think. HasAndroidInjector was introduced in 2.23, which can take any arbitrary type. The only thing that you'll need is some way to locate the relevant AndroidInjector now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment