Provide third party class instance using Hilt - android

I'm using Googles mlkit to scan barcodes. I've included some of their sample files. I would like to inject BarcodeScannerProcessor below using Hilt, rather than creating an inline instance.
I've tried flagging it as #ActivityScoped and injecting #ActivityContext context: Context in its constructor, which as far as I know should be eqvivalent to the inline instance below. However, when I do that and supply it to the activity below, I just get a black scanner window. How can I provide BarcodeScannerProcessor using Hilt?
class ScannerActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_vision_live_preview)
// some rows omitted for brevity
val processor = BarcodeScannerProcessor(this)
cameraSource!!.setMachineLearningFrameProcessor(processor)
}
}
My attempt to modify the BarcodeScannerProcessor:
#ActivityScoped
class BarcodeScannerProcessor #Inject constructor(#ActivityContext context: Context)
: VisionProcessorBase<List<Barcode>>(context) {
//....
Injected into the ScannerActivity using:
#Inject lateinit var barcodeScannerProcessor: BarcodeScannerProcessor

Related

ViewModel injection failing on Dagger + Kotlin Android

class MovieListFragment : Fragment() {
#Inject
lateinit var movieListView: MovieListViewModel
private lateinit var movieListAdapter: MovieListAdapter
private lateinit var binding: ListFragmentBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerMovieComponent.builder().appComponent(MovieListApp.component()).fragmentModule(FragmentModule(this)).build().inject(this)
}
This is the class I'm trying to have my viewmodel injected.
#Module (includes = [FragmentModule::class])
class MovieListModule(fragment: Fragment) {
private lateinit var movieListView : MovieListViewModel
#Provides
fun getMovieListViewModel(fragment: Fragment): MovieListViewModel {
movieListView = ViewModelProvider(fragment).get(MovieListViewModel::class.java)
return movieListView
}
}
This is the class that has the module and lastly,
#Singleton
#Component(modules = [MovieModule::class,MovieListModule::class], dependencies = [AppComponent::class]))
interface MovieComponent {
fun inject(movieListViewModel : MovieListViewModel)
fun inject(movieDetailViewModel: MovieDetailViewModel)
fun inject(fragment : Fragment)
}
This is my component interface.
The app crashes, saying that the lateinit viewmodel that was supposed to be injected is not initialised. Is there a way around this?
Thank you in advance.
The error message:
2022-03-30 15:41:40.749 18607-18607/com.example.polyapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.polyapp, PID: 18607
java.lang.RuntimeException: Unable to create application com.example.polyapp.MovieListApp: java.lang.IllegalStateException: com.example.polyapp.movieDatabaseFeature.di.AppComponent must be set
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7487)
at android.app.ActivityThread.access$1700(ActivityThread.java:310)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2283)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8641)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1133)
Caused by: java.lang.IllegalStateException: com.example.polyapp.movieDatabaseFeature.di.AppComponent must be set
at dagger.internal.Preconditions.checkBuilderRequirement(Preconditions.java:95)
at com.example.polyapp.movieDatabaseFeature.di.DaggerMovieComponent$Builder.build(DaggerMovieComponent.java:101)
at com.example.polyapp.MovieListApp.onCreate(MovieListApp.kt:15)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1211)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7482)
at android.app.ActivityThread.access$1700(ActivityThread.java:310) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2283) 
at android.os.Handler.dispatchMessage(Handler.java:106) 
at android.os.Looper.loopOnce(Looper.java:226) 
at android.os.Looper.loop(Looper.java:313) 
at android.app.ActivityThread.main(ActivityThread.java:8641) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1133) 
ViewModel injection on Android is tricky because ViewModels are created using ViewModelProvider to ensure they survive configuration changes. If they're created with ViewModelProvider, then how do you create it with Dagger? Luckily they both provide API's that can mesh together to solve your problem.
Dagger has Multibindings, and ViewModelProvider has it's ViewModelProvider.Factory API. Multibindings allow us to more finely tune when injection occurs by looking it up on a map first. The ViewModelProvider.Factory will tell the ViewModelProvider how to construct your ViewModel which allows for you to specify constructor parameters.
Here are the steps and explanations:
Annotate your MovieListViewModel constructor with #Inject. This will tell Dagger to put your MovieListViewModel on its' graph provided it can satisfy the constructor parameters(we won't be injecting it directly, we just need it on the Dagger graph). If there are no parameters, Dagger will handle it just fine.
import javax.inject.Inject
class MovieListViewModel #Inject constructor() {
...
}
Create a Multibinding for your MovieListViewModel. Instead of directly injecting your MovieListViewModel into the fragment, we want to wrap it in a special Dagger feature called a Multibinding. This will allow you to put your MovieListViewModel into a Map which can be injected and more finely manipulated at runtime(remember that ViewModelProvider.Factory I mentioned?).
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
annotation class ViewModelKey(val value: KClass<out ViewModel>)
#Module
abstract class MovieListViewModelMultiBinder
{
#Binds // Tells Dagger to use the parameter value here, which extends ViewModel
#IntoMap // Tells Dagger to put this ViewModel implementation into a map. This requires you to provide a key which is known at compile time.
#ViewModelKey(MovieListViewModel::class) // Tells Dagger the key to use for this ViewModel. This is your ViewModel class.
// The parameter is the implementation to use when we request a `ViewModel`. Since this is a multibinding, multiple ViewModels can be bound.
fun bind(viewModel: MovieListViewModel): ViewModel
}
Create a ViewModelProvider.Factory that injects and uses the Map mentioned in step 2. This uses a special Dagger type called a Provider. Providers wrap your injected type and do not construct it until you call Provider.get() to retrieve your object.
class DaggerViewModelFactory #Inject constructor(
private val viewModelProviders: Map<Class<out ViewModel>, Provider<ViewModel>>
): ViewModelProvider.Factory
{
override fun <T: ViewModel?> create(modelClass: Class<T>): T
{
return viewModelProviders[modelClass]?.get() as T
}
}
Use your custom ViewModelProvider.Factory in your Fragments and Activities.
class MyActivity: Activity() {
private lateinit var viewModel: MovieListViewModel
#Inject lateinit var factory: DaggerViewModelFactory
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this, factory).get(MovieListViewModel::class.java)
}
}
For the sake of simplicity, I did not throw an error if the DaggerViewModelFactory returns null for the modelClass, but you should add one in case you forget to bind your ViewModel into the Multibinding.
Hope this helps.

Hilt injection into activity before super.onCreate()

I defined my own LayoutInflater.Factory2 class in a separate module. I want to inject it into each activity in my App, but the point is that I have to set this factory before activity's super.onCreate() method.
When I using Hilt it makes an injection right after super.onCreate(). So I have an UninitializedPropertyAccessException.
Is there any opportunity to have an injection before super.onCreate with Hilt?
Below is my example of module's di.
#Module
#InstallIn(SingletonComponent::class)
object DynamicThemeModule {
#FlowPreview
#Singleton
#Provides
fun provideDynamicThemeConfigurator(
repository: AttrRepository
): DynamicTheme<AttrInfo> {
return DynamicThemeConfigurator(repository)
}
}
You can inject the class before onCreate by using Entry Points like this.
#AndroidEntryPoint
class MainActivity: AppCompatActivity() {
#EntryPoint
#InstallIn(SingletonComponent::class)
interface DynamicThemeFactory {
fun getDynamicTheme() : DynamicTheme<AttrInfo>
}
override fun onCreate(savedInstanceState: Bundle?) {
val factory = EntryPointAccessors.fromApplication(this, DynamicThemeFactory::class.java)
val dynamicTheme = factory.getDynamicTheme()
super.onCreate(savedInstanceState)
}
}
If you need something like this a lot Id recommend creating an instance of it in the companion object of your Application class when your application starts (onCreate). That is before any of your views are created. So you don´t need to jump threw those hoops all the time, but can just access the instance that already exists. This code above won´t be available in attachBaseContext, when you need it there you have to create it in your application class I think.

Is there a way to use injectors in a non-(Activity,Service,Fragment, Application) class

We're using Dagger2 in our application. I am trying to do a room database and I am writing the repository code, but I would like to inject application context and the DAO for the class.
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
Here's what I have:
class DownloadsDataRepositoryImpl : IDownloadsDataRepository, HasAndroidInjector {
#Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
#Inject
lateinit var downloadsDao: DownloadsDao
override fun androidInjector(): AndroidInjector<Any> = androidInjector
init {
androidInjector()
}
}
But I'm sure it's not going to work. Is there a way to do it?
As stated, dagger-android is just a tool to help injecting specific framework classes that you can't have control on it's creation.
The proper approach is to use simple construction injection.
To be more direct on how you should expose it on your #Component, I would need more code, specifically on what you have on your activity/fragment, but here is a crude example (that I did not tested, if there are minor errors, you can fix them following the compiler error messages):
First, you will have some object that exposes your DAO. Probably it's room?
#Entity(tableName = "download_table")
data class DownloadEntity(
#PrimaryKey
val key: String
)
#Dao
interface DownloadsDao {
#Query("SELECT * FROM download_table")
fun load(): List<DownloadEntity>
}
#Database(
entities = [DownloadEntity::class], version = 1
)
abstract class DownloadRoomDatabase : RoomDatabase() {
abstract val downloadsDao: DownloadsDao
}
Now we will create a crude repository that is build with #Inject annotation. Dagger will take care of building this object for us. Notice that I am not using dagger-android for it:
interface IDownloadsDataRepository
class DownloadsDataRepositoryImpl #Inject constructor(
val downloadsDao: DownloadsDao
) : IDownloadsDataRepository
How to expose it to your activity/fragment/service requires more details on your implementation. For example, if it's inside a ViewModel or a Presenter that is annotated with #Inject or you are accessing directly on your activity will result in different implementations. Without more details, I will suppose that you are accessing the repository directly on your activity:
class DownloadActivity : FragmentActivity() {
#Inject
lateinit val repo: IDownloadsDataRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerDownloadComponent.factory().create(this).inject(this)
}
}
Now we need to instruct Dagger on how to:
Bind your concrete DownloadsDataRepositoryImpl to the IDownloadsDataRepository interface that the activity requires
How to provide the dependencies to build DownloadsDataRepositoryImpl
For this we will need a module:
#Module
abstract class RepositoryModule {
//We will bind our actual implementation to the IDownloadsDataRepository
#Binds
abstract fun bindRepo(repo: DownloadsDataRepositoryImpl): IDownloadsDataRepository
#Module
companion object {
//We need the database to get access to the DAO
#Provides
#JvmStatic
fun provideDataBase(context: Context): DownloadRoomDatabase =
Room.databaseBuilder(
context,
DownloadRoomDatabase::class.java,
"download_database.db"
).build()
//With the database, we can provide the DAO:
#Provides
#JvmStatic
fun provideDao(db: DownloadRoomDatabase): DownloadsDao = db.downloadsDao
}
}
With this, we can finish the last part of our puzzle, creating the #Component:
#Component(
modules = [
RepositoryModule::class
]
)
interface DownloadComponent {
fun inject(activity: DownloadActivity)
#Component.Factory
interface Factory {
fun create(context: Context): DownloadComponent
}
}
Notice that I did not use any dagger-android code, I don't think it's useful and causes more confusion than necessary. Stick with basic dagger2 constructs and you are fine. You can implement 99.9% of your app only understanding how those constructs works:
#Module, #Component and #Subcomponent
Edit: As stated in the comments, probably you will need to properly manage the scope of your repository, specially the DB creation if you are actually using Room.
Not sure how you implemented dagger, but here is an example how you can provide context to non activity class.
Suppose you have AppModule class, so there you can add provideContext() method:
#Module
class AppModule(app: App) {
private var application: Application = app
#Provides
fun provideContext(): Context {
return application
}
}
and here is non activity class written in Kotlin:
class Utils #inject constructor(private val context: Context) {
..
}
And that's it, just rebuild j
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
You were correct to assume that before Dagger-Android 2.20, but not after 2.20+.
Now you can create a #ContributesAndroidInjector for any class, which will generate an AndroidInjector<T> for that T for which you added #ContributesAndroidInjector.
This means that there is a multi-binding that allows you to get an AndroidInjector<T> for a T, and this is what HasAndroidInjector does for you.
So the following worked for me in a different scenario (for member-injecting Workers in work-manager, instead of creating a multi-binding and a factory):
#Keep
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
init {
val injector = context.applicationContext as HasAndroidInjector
injector.androidInjector().inject(this)
}
#Inject
lateinit var apiService: ApiService
and
#ContributesAndroidInjector
abstract fun syncWorker(): SyncWorker
HOWEVER in your particular case, none of this is required.
Dagger-Android is for member-injecting classes using an auto-generated subcomponent, that you typically need only if your injected type is inside a different module, and therefore you can't directly add fun inject(T t) into your AppComponent, OR you don't see your AppComponent.
In your case, simple constructor injection is enough, as you own your own class.
#Singleton
class DownloadsDataRepositoryImpl #Inject constructor(
private val downloadsDao: DownloadsDao
): IDownloadsDataRepository {}
Which you can bind via a module
#Module
abstract class DownloadsModule {
#Binds
abstract fun dataRepository(impl: DownloadsDataRepositoryImpl): IDownloadsDataRepository
}
And otherwise you just create your component instance inside Application.onCreate()
#Component(modules = [DownloadsModule::class])
#Singleton
interface AppComponent {
fun dataRepository(): DownloadsDataRepository
#Component.Factory
interface Factory {
fun create(#BindsInstance appContext: Context): AppComponent
}
}
And
class CustomApplication: Application() {
lateinit var component: AppComponent
private set
override fun onCreate() {
super.onCreate()
component = DaggerAppComponent.factory().create(this)
}
}
Then you can get it as
val component = (context.applicationContext as CustomApplication).component
Though technically you may as well create an extension function
val Context.appComponent: AppComponent
get() = (applicationContext as CustomApplication).component
val component = context.appComponent

Koin how to inject outside of Android activity / appcompatactivity

Koin is a new, lightweight library for DI and can be used in Android as well as in standalone kotlin apps.
Usually you inject dependencies like this:
class SplashScreenActivity : Activity() {
val sampleClass : SampleClass by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
with the inject() method.
But what about injecting stuff in places where Activity context is not available i.e. outside of an Activity?
There is the KoinComponent which comes to the rescue. In any class you can simply:
class SampleClass : KoinComponent {
val a : A? by inject()
val b : B? by inject()
}
Extending KoinComponent gives you access to inject() method.
Remember that usually it's enough to inject stuff the usual way:
class SampleClass(val a : A?, val b: B?)
Koin provides a solution for this using the KoinComponent interface. For example, if you need to get some dependencies in your repository then you can simply implement the KoinComponent interface. It gives you access to various Koin features such as get() and inject(). Use KoinComponent only when you can't rewrite the constructor to accept dependencies as constructor parameters.
class MyRepository: Repository(), KoinComponent {
private val myService by inject<MyService>()
}
Constructor injection is better than this approach.
For example, the same thing can be achieved by:
class MyRepository(private val service: MyService): Repository() {
...
}
And you can add the definition for instantiating this class in a koin module:
val serviceModule = module {
...
factory { MyService() }
}
val repositoryModule = module {
...
factory { MyRepository(get<MyService>()) }
}
If you don't want to implement any interfaces then just take a look at how KoinComponent.inject() is implemented and do something similar yourself:
val foo by lazy { KoinPlatformTools.defaultContext().get().get<FooClass>() }

Dagger can not provide injection with Kotlin

I have this issue when I try to use Kotlin and Dagger 2 .
"interface cannot be provided without an #Provides- or #Produces-annotated method.”
This is my Module class:
#Module
class MenuActivityModule(#NonNull private val menuActivity: MenuActivity) {
#Provides
#MenuActivityScope
fun provideGameScreenDimensions(application: Application) =
GameScreenDimension(application.resources)
#Provides
#MenuActivityScope
fun provideAudio() =
AndroidAudio(menuActivity)
#Provides
#MenuActivityScope
fun providePowerManager() =
menuActivity.getSystemService(Context.POWER_SERVICE) as PowerManager
#Provides
#MenuActivityScope
fun provideWakeLock(#NonNull powerManager: PowerManager) =
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Preferences.APPLICATION_TAG)
}
This is a part of my Activity class, where I inject some variables with Dagger:
class MenuActivity : BaseActivity {
#Inject
lateinit var myAudio: Audio
#Inject
lateinit var wakeLock: PowerManager.WakeLock
#Inject
lateinit var apiService : ApiService
#Inject
lateinit var sharedPref : SharedPreferences
#Inject
lateinit var gameDimension : GameScreenDimension
init {
DaggerMenuActivityComponent.builder()
.menuActivityModule(MenuActivityModule(this))
.build()
.inject(this)
}
//more code
}
Audio.kt is interface and Dagger has problem to inject it. Inside the activity module I am returning AndroidAudio
instance, which implements Audio interface. I am not sure what is the problem here. In Java I have had many times injection of interfaces and I never had this issue before.
If somebody can help me I will be so happy.
Thanks!
I think the solution for your problem is very simple and also not so obvious unfortunately.
Because Kotlin does not require type to be specified on methods return, you can easily write something like this:
#Provides
#MenuActivityScope
fun provideAudio() =
AndroidAudio(menuActivity)
And the compiler will not complain about that, but in this case Dagger will provide AndroidAudio object for injection. In you Activity you are looking for Audio object for injection. So if you change this code to be:
#Provides
#MenuActivityScope
fun provideAudio(): Audio =
AndroidAudio(menuActivity)
Everything should be ОК.
Give a try and tell me if something does not work.
Thanks.
BTW : When I use Dagger with Kotlin I aways specify the type of returned value, because usually that is gonna be the type of the injected variables or the type of the variable which you are going to use in your dagger module.

Categories

Resources