Green Ho
Green Ho

Reputation: 901

Dagger injection for sublclass

I have @inject in a base class and thus all subclasses will inject that dependency from the base class, then I hit an issue where it says "You must explicitly add it to the 'injects' option in one of your modules".

Explicitly adding all subclasses to the injects option does fix the issue but then I will need to make sure whenever I have a new subclass I will have to add the new subclass to "injects", or will get the exception. Is there an easy way to handle that?

Thanks!

Upvotes: 0

Views: 55

Answers (1)

LordRaydenMK
LordRaydenMK

Reputation: 13321

If I understand correctly you want to call inject(base activity) but your @Inject annotated fields are in classes sub-classing the base activity.

There is one solution based on reflection (so probably it would break ProGuard). The solution is described in this blog post.

package info.android15.dagger2example;

import java.lang.reflect.Method;
import java.util.HashMap;

public class Dagger2Helper {

    private static HashMap<Class<?>, HashMap<Class<?>, Method>> methodsCache = new HashMap<>();

    /**
     * This method is based on https://github.com/square/mortar/blob/master/dagger2support/src/main/java/mortar/dagger2support/Dagger2.java
     * file that has been released with Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ by Square, Inc.
     * <p/>
     * Magic method that creates a component with its dependencies set, by reflection. Relies on
     * Dagger2 naming conventions.
     */
    public static <T> T buildComponent(Class<T> componentClass, Object... dependencies) {
        buildMethodsCache(componentClass);

        String fqn = componentClass.getName();

        String packageName = componentClass.getPackage().getName();
        // Accounts for inner classes, ie MyApplication$Component
        String simpleName = fqn.substring(packageName.length() + 1);
        String generatedName = (packageName + ".Dagger" + simpleName).replace('$', '_');

        try {
            Class<?> generatedClass = Class.forName(generatedName);
            Object builder = generatedClass.getMethod("builder").invoke(null);

            for (Method method : builder.getClass().getMethods()) {
                Class<?>[] params = method.getParameterTypes();
                if (params.length == 1) {
                    Class<?> dependencyClass = params[0];
                    for (Object dependency : dependencies) {
                        if (dependencyClass.isAssignableFrom(dependency.getClass())) {
                            method.invoke(builder, dependency);
                            break;
                        }
                    }
                }
            }
            //noinspection unchecked
            return (T)builder.getClass().getMethod("build").invoke(builder);
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static <T> void buildMethodsCache(Class<T> componentClass) {
        if (!Dagger2Helper.methodsCache.containsKey(componentClass)) {
            HashMap<Class<?>, Method> methods = new HashMap<>();
            for (Method method : componentClass.getMethods()) {
                Class<?>[] params = method.getParameterTypes();
                if (params.length == 1)
                    methods.put(params[0], method);
            }
            Dagger2Helper.methodsCache.put(componentClass, methods);
        }
    }

    public static void inject(Class<?> componentClass, Object component, Object target) {

        HashMap<Class<?>, Method> methods = methodsCache.get(componentClass);
        if (methods == null)
            throw new RuntimeException("Component " + componentClass + " has not been built with " + Dagger2Helper.class);

        Class targetClass = target.getClass();
        Method method = methods.get(targetClass);
        if (method == null)
            throw new RuntimeException("Method for " + targetClass + " injection does not exist in " + componentClass);

        try {
            method.invoke(component, target);
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

then in your base activity you can do this:

Dagger2Helper.inject(AppComponent.class, component, this);

I don't think a little bit of reflection would affect performance too much, for me the real issue is breaking ProGuard.

Upvotes: 1

Related Questions