Hilt Injection not working with BroadcastReceiver

Viewed 10313

Dependency injection inside BroadcastReceiver using Hilt isn't working. I try to invoke a BroadcastReceiver using an alarm from MainActivity and i am getting UninitializedPropertyAccessException. According to the documentation it should be as simple as adding the @AndroidEntryPoint annotation to the receiver but its not.

Sample Code:

App.kt:

@HiltAndroidApp
class App: Application() {
    override fun onCreate() {
        super.onCreate()
        Log.d(App::class.simpleName, "onCreate: Application")
    }
}

TestHiltInjection.kt:

class TestHiltInjection @Inject constructor() {

    operator fun invoke() {
        Log.d(TestHiltInjection::class.java.simpleName, "invoke called.")
    }
}

HiltBroadcastReceiver.kt:

@AndroidEntryPoint
class HiltBroadcastReceiver : BroadcastReceiver() {

    @Inject lateinit var testHiltInjection: TestHiltInjection

    override fun onReceive(context: Context?, intent: Intent?) {
        testHiltInjection()
    }
}

MainActivity.kt:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val manager = getSystemService(Context.ALARM_SERVICE) as AlarmManager

        val pending = PendingIntent
            .getBroadcast(this, 0, Intent(this, HiltBroadcastReceiver::class.java), 0)
        manager.setInexactRepeating(
            AlarmManager.ELAPSED_REALTIME_WAKEUP,
            15000, 15000,
            pending
        )
    }
}

Output Logcat:

kotlin.UninitializedPropertyAccessException: lateinit property testHiltInjection has not been initialized

Update

Issue was already solved on 2.29.1 version, just use @AndroidEntryPoint

5 Answers

Update: According to the issue the problem should be fixed in version 2.29.1 of Dagger Hilt. So, just use version 2.29.1-alpha or above. Don't forget to update hilt-android-gradle-plugin version as well.


Original answer: There's a GitHub issue and a workaround. It seems that injection doesn't work because it actually happens inside onReceive() method of the generated parent class. The problem is that you cannot call the super method since it's abstract. But you can create a simple wrapper class that fixes the problem:

abstract class HiltBroadcastReceiver : BroadcastReceiver() {
  @CallSuper
  override fun onReceive(context: Context, intent: Intent) {}
}

@AndroidEntryPoint
class MyBroadcastReceiver : HiltBroadcastReceiver() {
  @Inject lateinit var testHiltInjection: TestHiltInjection

  override fun onReceive(context: Context?, intent: Intent?) {
    super.onReceive(context, intent) // <-- it's the trick

    ...
  }
}

I think hilt currently doesn't support BroadcastReceiver. In https://dagger.dev/hilt/android-entry-point says

Note: Hilt currently only supports activities that extend ComponentActivity and fragments that extend androidx library Fragment, not the (now deprecated) Fragment in the Android platform.

How it works internally: Hilt creates abstract class for @AndroidEntryPoint annotated component(Activity, Fragment, BroadcastReceiver etc) and your BroadcastReceiver extends generated class as base class in bytecode transformation. In base class onReceive method object is injected. But in generated bytecode class super class onReceive method is not called. That's why your object is not injected. To test add below code before testHiltInjection() in HiltBroadcastReceiver class. By the way it's still in Alpha mode.

((HiltBroadcastReceiver_GeneratedInjector) BroadcastReceiverComponentManager.generatedComponent(context)).injectHiltBroadcastReceiver(UnsafeCasts.<HiltBroadcastReceiver>unsafeCast(this));

Update: Now issue has been fixed in version 2.29.1-alpha. Also do not forget to upgrade hilt-android-gradle-plugin version to 2.29.1-alpha or latest. More info on releases .

Fix #1918: Support BroadcastReceiver with @AndroidEntryPoint transform. (ede018b)

With the stable version of Hilt. you can easily inject any variable like this

@AndroidEntryPoint
public class SMSReceiver extends BroadcastReceiver {
    @Inject
    public DataManager dataManager;

    @Override
    public void onReceive(final Context context, Intent intent) {
    }
}

Currently, you can't use @AndroidEntryPoint for BroadcastReceiver due to Hilt bug. instead you use workaround logic like this

if you want to inject SomeModule in BroadcastReceiver

  1. create new EntryPoint

    @EntryPoint
    @InstallIn(ApplicationComponent::class)
    interface MyEntryPoint {
        fun getSomeModule: SomeModule
    }
    
  2. get entrypoint and use it

    class MyBroadcastReceiver : BroadcastReceiver() {
       override fun onReceive(context: Context?, intent: Intent?) {
        val someModule : SomeModule =
          EntryPoints.get(context?.applicationContext, MyEntryPoint::class.java)
       .getSomeModule()
      }
    }
    

Hilt is now stable. its now as simple as:

@AndroidEntryPoint
class MyAppWidget : AppWidgetProvider() {

@Inject
lateinit var getMyUseCase: MyUseCase
Related