30255

Android Dagger 2 with BaseActivity to reduce boilerplate

I'm having some troubles while I want to move some Dagger 2 boilerplate code in each activity to a BaseActivity.

BaseActivity extends AppCompatActivity

I have multiples activities, like:

ActivityA extends BaseActivity implements InterfaceA; ActivityB extends BaseActivity implements InterfaceB; ...

In each activity I have a methods like this (where X is A, B, C, ... for each activity):

public void initActivity() { ComponentX compX; ... compX = appComponent.plus(new ModuleX(this)); // this == InterfaceX ... compX.inject(this); // this == ActivityX }

I was trying to reduce this code, moving it to the parent BaseActivity. But I'm having some problems to do it. I think that maybe with generics I could do it, but I don't know exactly how.

Answer1:

Here's the question: Which inject method are you calling, and can Java determine that at compile time?

As described in "A note about covariance", Dagger will generate code for any members-injection method you define, but only the static type you pass in.

@Component public interface YourComponent { void injectBase(BaseActivity baseActivity); void injectA(ActivityA activityA); void injectB(ActivityB activityB); void injectC(ActivityC activityC); }

When calling injectA and passing an ActivityA instance, you'll get injection for the fields defined in ActivityA including the fields in BaseActivity. Same with ActivityB and ActivityC. However, if you call injectBase, Dagger will only inject the fields belonging to BaseActivity, even if the object you pass in happens to be an ActivityA, ActivityB, or ActivityC. Dagger generates code at compile time, so if you call injectBase, the injection will only happen for the fields on BaseActivity—because that's the code that was generated for BaseActivity's members injector, and those are the only fields Dagger knows how to inject for a BaseActivity parameter.

Naturally, because BaseActivity only knows that this is a subtype of BaseActivity, it can only call injectBase and not any specific subtypes. Importantly, this remains true even if all the names injectBase, injectA, and so forth, are all the same (like inject). The JVM will pick the narrowest overload it can determine at compile time, which will be inject(BaseActivity), which will inject BaseActivity's fields and nothing in subtypes. If you were to name them uniquely, you'd see which one you're calling, and why it's not injecting subtype fields.

Generics won't help here: You're looking for your Component to generate and call members injectors for ActivityA, ActivityB, and ActivityC. Generics will be erased, but furthermore the component can't take an arbitrary subclass of BaseActivity: Dagger can't generate code at compile time for types it might only encounter at runtime. You really need to prepare those types in Dagger at compile time.

One way around this is to allow the subtypes to inject themselves. The subtypes know that this is ActivityA (and so forth), and even though the code might look character-for-character the same, Java can identify the right type and compile it correctly.

// in BaseActivity protected abstract void injectDependencies(); // in ActivityA @Override protected void injectDependencies() { component.injectA(this); }

However, there's another recently-released option, using dagger.android, which uses Multibindings and (effectively) a Map<Class, MembersInjector> to dynamically inject the specific type you want. This works from a superclass, too, to the point that you can have your Activity extend DaggerActivity and everything will work just the way you'd like. (Consult the dagger.android.support package for your AppCompatActivity equivalent DaggerAppCompatActivity.)

Recommend

  • Dependency Injection - Choose DLL and class implementation at runtime through configuration file
  • MySQL query based on user input
  • How to design Java class(es) that can optionally function as Singleton?
  • Dependency Injection and Code Obfuscation
  • True privateness in Python
  • What is the right syntax for “timeframe” in MailChimp API 3.0
  • HALF_PTR Windows data type
  • How to determine the CCSID used in CPYFRMIMPF command?
  • How to discover Font Type?
  • JSR-330 support in Picocontainer : @Inject … @Named(\"xxx)
  • Creating a DropDownList
  • Who propagate bugfixes across branches (corporate development)?
  • SetWindowsHookEx does not react on media keys
  • How can I enlarge video fullscreen without the affected interface project in as3?
  • Sencha Touch 2.0 Controller refs attribute not working?
  • Typescript - Unable to get 'import' statement to function
  • The plugin 'org.apache.maven.plugins:maven-jboss-as-plugin' does not exist or no valid ver
  • Scrapy recursive link crawler
  • How to rebase a series of branches?
  • When to use `image` and when to use `Matrix` in Emgu CV?
  • Disabling Alt-F4 on a Win Forms NotifyIcon
  • What is the “return” in scheme?
  • How to add a column to a Pandas dataframe made of arrays of the n-preceding values of another column
  • Updating server-side rendering client-side
  • Cross-Platform Protobuf Serialization
  • sending/ receiving email in Java
  • Which linear programming package should I use for high numbers of constraints and “warm starts” [clo
  • How to format a variable of double type
  • How to delete a row from a dynamic generate table using jquery?
  • json Serialization in asp
  • Proper way to use connect-multiparty with express.js?
  • How can I get HTML syntax highlighting in my editor for CakePHP?
  • Why joiner is not used after Sequence generator or Update statergy
  • coudnt use logback because of log4j
  • How do I configure my settings file to work with unit tests?
  • IndexOutOfRangeException on multidimensional array despite using GetLength check
  • JaxB to read class hierarchy
  • Checking variable from a different class in C#
  • Binding checkboxes to object values in AngularJs
  • Android Heatmap on canvas or ImageView