google / dagger

A fast dependency injector for Android and Java.
https://dagger.dev
Apache License 2.0
17.36k stars 2k forks source link

Hilt not working with AbstractComposeView for Jepack Compose #3418

Open frhnfrq opened 2 years ago

frhnfrq commented 2 years ago

I am using DaggerHilt for dependency injection. In my AbstractComposeView, I need to access the ViewModel in a Composable function. To do that I have to annotate my AbstractComposeView with AndroidEntryPoint, since my Composable is part of the AbstractComposeView (which is being used in a Service with AndroidEntryPoint). but I am getting this error.

error: [Hilt] The base class, 'androidx.compose.ui.platform.AbstractComposeView', of the @AndroidEntryPoint, 'com.qwillio.vendi.keyboard.presentation.KeyboardView', contains a constructor with default parameters. This is currently not supported by the Gradle plugin. Either specify the base class as described at https://dagger.dev/hilt/gradle-setup#why-use-the-plugin or remove the default value declaration. [Hilt] Processing did not complete. See error above for details. [Hilt]

This is my AbstractComposeView,

@AndroidEntryPoint
class KeyboardView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {

    private var keyboardActionListener: OnKeyboardActionListener? = null

    fun setOnKeyboardActionListener(keyboardActionListener: OnKeyboardActionListener) {
        this.keyboardActionListener = keyboardActionListener
    }

    @Composable
    override fun Content() {
        Vendiboard {
            keyboardActionListener?.onKeyboardAction(it)
        }
    }
}

build.gradle

    implementation "com.google.dagger:hilt-android:2.40.5"
    kapt "com.google.dagger:hilt-android-compiler:2.40.5"
    implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
    kapt "androidx.hilt:hilt-compiler:1.0.0"
    implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
kuanyingchou commented 2 years ago

It should work with specifying the base class in the AndroidEntryPoint annotation:

@AndroidEntryPoint(AbstractComposeView::class)
class KeyboardView(context: Context): Hilt_KeyboardView(context) {
  @Composable
  override fun Content() {
    // ...
  }
}

For this to work, you may also need to add this flag to build.gradle for kapt(see here):

kapt { correctErrorTypes true }

Hope this helps.

frhnfrq commented 2 years ago

@kuanyingchou Thanks, your solution worked.

Can you please answer one more thing?

I am getting this error now,

java.lang.ClassCastException: Cannot cast 
com.qwillio.vendi.DaggerVendiApplication_HiltComponents_SingletonC$ServiceCImpl to 
dagger.hilt.android.internal.managers.ViewComponentManager$ViewComponentBuilderEntryPoint

I am using the view in my InputMethodService here,


@AndroidEntryPoint
class VendiboardService : InputMethodService(), OnKeyboardActionListener {

    private lateinit var lifecycleManager: VendiboardLifecycleManager

    override fun onCreateInputView(): View {
        val view = KeyboardView(this)
        view.setOnKeyboardActionListener(this)
        lifecycleManager.setDecorView(window!!.window!!.decorView)
        lifecycleManager.setKeyboardView(view)

        return view
    }

    override fun onKeyboardAction(action: String) {
        currentInputConnection.also { ic: InputConnection ->
            ic.commitText(action, 1)
        }
    }

    override fun onCreate() {
        super.onCreate()
        lifecycleManager = VendiboardLifecycleManager()
        lifecycleManager.getSavedStateRegistryController().performRestore(null)
        lifecycleManager.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
    }

    override fun onDestroy() {
        super.onDestroy()
        lifecycleManager.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    }

}

If I pass baseContext to the view instead, I get

java.lang.IllegalStateException: class com.qwillio.vendi.keyboard.presentation.KeyboardView,
 Hilt view must be attached to an @AndroidEntryPoint Fragment or Activity.

How do I make Hilt view work with Service?

kuanyingchou commented 2 years ago

Hmm...this looks similar to this one: https://github.com/google/dagger/issues/3205

frhnfrq commented 2 years ago

Any chance of making Dagger Hilt views work with Services by default?

Arxing commented 1 year ago

The following code can solve your problem.

class BaseAbstractComposeView : AbstractComposeView {
    constructor(context: Context) : super(context, null, 0)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}

@AndroidEntryPoint
class KeyboardView : BaseAbstractComposeView {

    constructor(context: Context) : super(context, null, 0)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    private var keyboardActionListener: OnKeyboardActionListener? = null

    fun setOnKeyboardActionListener(keyboardActionListener: OnKeyboardActionListener) {
        this.keyboardActionListener = keyboardActionListener
    }

    @Composable
    override fun Content() {
        Vendiboard {
            keyboardActionListener?.onKeyboardAction(it)
        }
    }
}
mobilekosmos commented 1 year ago

kapt { correctErrorTypes true }

Don't know why OP said that this worked, in my base I'm getting: One type argument expected for class Hilt_MyFragment<Result : FeatureResult!>

codejunk1e commented 8 months ago

kapt { correctErrorTypes true }

Don't know why OP said that this worked, in my base I'm getting: One type argument expected for class Hilt_MyFragment<Result : FeatureResult!>

Having this same issue. Fix OP said works does not work

frhnfrq commented 8 months ago

@mobilekosmos @codejunk1e Please read my whole text, what I meant was that the solution suggested @kuanyingchou only fixed the error I was having at that time, it later introduced other errors.

Anyway, basically you can't use hilt with Views inflated from Services. You'll have to get the dependencies in your service then pass it to your view. That's the only soluion afaik.

levon93 commented 2 months ago

Until this gets supported via the Hilt Gradle Plugin, you can use EntryPoint to inject your view

class KeyboardView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {

   init {
      EntryPointAccessors.fromActivity(
          activity = context.findActivity() as Activity,
          entryPoint = KeyboardViewEntryPoint::class.java,
      ).inject(this)
    }

    @EntryPoint
    @InstallIn(ActivityComponent::class)
    internal interface KeyboardViewEntryPoint {
        fun inject(keyboardView: KeyboardView)
    }
}