Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dagger Android for custom class possible?

I am trying to make dagger-android work with Conductor (or any custom class). I tried replicating everything that AndroidSupportInjectionModule (and friends) do, which in my mind is the same kind of a custom class handling.

However I get

C:\Users\ursus\AndroidStudioProjects\...\ControllersModule.java:15: error: com.foo.bar.ChannelsController is not a framework type
    public abstract com.foo.bar.ChannelsController channelsController();

So, my "library" code

package com.foo.bar

import com.bluelinelabs.conductor.Controller;
import dagger.Module;
import dagger.android.AndroidInjectionModule;
import dagger.android.AndroidInjector;
import dagger.internal.Beta;
import dagger.multibindings.Multibinds;

import java.util.Map;

@Beta
@Module(includes = AndroidInjectionModule.class)
public abstract class ConductorInjectionModule {

    private ConductorInjectionModule() {
    }

    @Multibinds
    abstract Map<Class<? extends Controller>, AndroidInjector.Factory<? extends Controller>> controllerInjectorFactories();

    @Multibinds
    abstract Map<String, AndroidInjector.Factory<? extends Controller>> controllerInjectorFactoriesWithStringKeys();
}

I dont even get compiled, so presuming pasting ConductorInjection & HasControllerInjector is pointless

Usage:

@Module
abstract class AppModule {
    @ContributesAndroidInjector abstract fun mainActivity(): MainActivity
    @ContributesAndroidInjector abstract fun channelsController(): ChannelsController
}

class App : Application(), HasActivityInjector, HasControllerInjector {

    @Inject lateinit var activityInjector: DispatchingAndroidInjector<Activity>
    @Inject lateinit var controllerInjector: DispatchingAndroidInjector<Controller>

    private lateinit var appComponent: AppComponent

    override fun onCreate() {
        super.onCreate()

        appComponent = DaggerAppComponent.builder()
            .applicationContext(this)
            .build()
            .apply {
                inject(this@App)
            }
    }

    override fun activityInjector() = activityInjector
    override fun controllerInjector() = controllerInjector
}

@Singleton
@Component(
    modules = [
        AndroidInjectionModule::class,
        ConductorInjectionModule::class,
        AppModule::class,
        NetModule::class
    ]
)
interface AppComponent {

    fun inject(app: App)

    @Component.Builder
    interface Builder {

        @BindsInstance
        fun applicationContext(context: Context): Builder

        fun build(): AppComponent
    }
}


implementation deps.dagger.runtime
implementation deps.dagger.androidRuntime
kapt deps.dagger.compiler
kapt deps.dagger.androidCompiler

where it is all "2.19" version (have tried 2.16)

AGP "com.android.tools.build:gradle:3.3.0-rc02" (have tried 3.2.1 stable)

Any clue? In my mind it all should work as its the same thing dagger-android-support does

like image 328
urSus Avatar asked Jan 26 '23 23:01

urSus


2 Answers

error: com.foo.bar.ChannelsController is not a framework type

So the question to answer is, "how does dagger-android know what a framework type is or not".

The answer can be found in this commit to Dagger-Android between 2.19 and 2.20, where they "removed the old way of doing things for better compatibility with AndroidX".

So as we can see in https://stackoverflow.com/a/53891780/2413303 ,

   /**    
    * Returns the Android framework types available to the compiler, keyed by their associated {@code 
    * dagger.android} {@link MapKey}s. This will always contain the types that are defined by the 
    * framework, and only contain the support library types if they are on the classpath of the   
    * current compilation.    
    */    
   static ImmutableMap<Class<? extends Annotation>, TypeMirror> frameworkTypesByMapKey(   
       Elements elements) {   
     return ImmutableMap.copyOf(  
         Stream.of(   
                 elements.getPackageElement("dagger.android"),    
                 elements.getPackageElement("dagger.android.support"))    
             .filter(packageElement -> packageElement != null)    
             .flatMap(packageElement -> typesIn(packageElement.getEnclosedElements()).stream())   
             .filter(AndroidMapKeys::isNotAndroidInjectionKey)    
             .filter(type -> isAnnotationPresent(type, MapKey.class)) 
             .filter(mapKey -> mapKey.getAnnotation(MapKey.class).unwrapValue())  
             .flatMap(AndroidMapKeys::classForAnnotationElement)  
             .collect(toMap(key -> key, key -> mapKeyValue(key, elements)))); 
   }

they had code that checked their own @MapKey types in dagger.android and dagger.android.support packages, that looked like this:

// java/dagger/android/support/FragmentKey.java

 @Beta  
 @MapKey    
 @Documented    
 @Target(METHOD)    
 @Deprecated    
 public @interface FragmentKey {    
   Class<? extends Fragment> value();   
 }

So they read the framework types based on what @MapKeys were available in the dagger.android and dagger.android.support packages.


Apparently they removed this check in 2.20 so now you can inject whatever you want. Rejoice!

But otherwise you could actually hack it in such a way that you'd add a @ControllerKey and a @ViewKey in dagger.android package in your project, and it'd actually likely work with 2.19.

The tests that were checking for errors in "is not a framework type" are also removed in that commit.

Ah, and

@Multibinds
abstract Map<String, AndroidInjector.Factory<? extends Controller>> controllerInjectorFactoriesWithStringKeys();

You can remove this part too with 2.20, all you need now is AndroidInjectionModule.

like image 89
EpicPandaForce Avatar answered Jan 29 '23 13:01

EpicPandaForce


For future travelers, they were hardcoding some appcompat stuff in the annot. processor, thats why appcompat fragments worked.

enter image description here

update to dagger 2.20, it will magically work

like image 32
urSus Avatar answered Jan 29 '23 14:01

urSus