Issue compiling with dependency injection - kotlin

Error with Kotlin conversion from java, this project is testing Github Api and displaying data in recyclerview.I get the a compile error which i cannot workout for Dagger2, it worked in Java but when converting to Kotlin i get a compile error at runtime.
It seems to be with injecting an api method into the view model
i have tried following the error then cleaning and rebuilding the app
I have also tried invalidating caches and restarting but seems there is an error with the conversion into Kotlin from Java. Any help would be appreciated.
Here is my class:
class RepoRepository {
private lateinit var repoService: GithubRepos
#Inject
fun RepoRepository(repoService: GithubRepos) {
this.repoService = repoService
}
fun getRepositories(): Single<List<Repo>> {
return repoService.getRepos()
}
fun getSingleRepo(owner: String, name: String): Single<Repo> {
return repoService.getSingleRepo(owner, name)
}
}
My component class:
#Singleton
#Component(modules = arrayOf(NetworkModule::class))
interface AppComponent {
/**
* inject required dependencies into MainActivityListViewModel
*/
fun inject(mainActivityListViewModel: MainActivityListViewModel)
#Component.Builder
interface Builder {
fun build(): AppComponent
fun networkModule(networkModule: NetworkModule): Builder
}
}
And my ViewModel:
class MainActivityListViewModel : BaseViewModel() {
private lateinit var repoRepository: RepoRepository
private var disposable: CompositeDisposable? = null
private val repos = MutableLiveData<List<Repo>>()
private val repoLoadError = MutableLiveData<Boolean>()
private val loading = MutableLiveData<Boolean>()
#Inject
fun ListViewModel(repoRepository: RepoRepository) {
this.repoRepository = repoRepository
disposable = CompositeDisposable()
fetchRepos()
}
fun getRepos(): LiveData<List<Repo>> {
return repos
}
fun getError(): LiveData<Boolean> {
return repoLoadError
}
fun getLoading(): LiveData<Boolean> {
return loading
}
private fun fetchRepos() {
loading.value = true
disposable?.add(repoRepository.getRepositories()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object :
DisposableSingleObserver<List<Repo>>() {
override fun onSuccess(value: List<Repo>) {
repoLoadError.value = false
repos.value = value
loading.value = false
}
override fun onError(e: Throwable) {
repoLoadError.value = true
loading.value = false
}
}))
}
override fun onCleared() {
super.onCleared()
if (disposable != null) {
disposable!!.clear()
disposable = null
}
}
}
this is error i am getting:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be
provided without an #Inject constructor or an #Provides-annotated
method. This type supports members injection but cannot be
implicitly provided.
public abstract repos.network.RepoRepository
repoRepository();
^
repos.network.RepoRepository is provided at
components.AppComponent.repoRepository() e: repos/di/components/AppComponent.java:19: error:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be
provided without an #Inject constructor or an #Provides- annotated
method. This type supports members injection but cannot be
implicitly provided.
^
repos.network.RepoRepository is injected at
repos.viewmodels.MainActivityListViewModel.ListViewModel(repoRepository)
repos.viewmodels.MainActivityListViewModel is injected at
repos.di.components.AppComponent.inject(repos.viewmodels.MainActivityListViewModel)

Your error clearly says:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be provided without an #Inject constructor or an #Provides-annotated method.
You didn't define constructor for your RepoRepository class.
It should look something like this:
class RepoRepository #Inject constructor(private val repoService: GithubRepos) {//the rest of your code here}
This goes for your viewmodel class as well.
If you are using android ViewModel architecture component i suggest you read this article which explains how to use it with Dagger2.
Hope this helps.

Related

Mockk #OverrideMockKs not working with Kotest

I've using Kotest recently and I hadn't had any issues, but recently I was trying some annotations for dependency injection so to simplify the problem I created some basic classes with some methods that just print some messages, just for the sake of learning how to use Kotest and Mockk, but during the testing, I ran with the exception that the variable hasn't been initialized when trying to run the test.
These are my classes
class DefaultClass : AbstractClass() {
private val anotherClass: AnotherClass = AnotherClass()
fun testMethod(value: String): String {
val normalizeValue = value.trim().lowercase().replace(Regex("[^ A-Za-z\\d]*"), "")
return runBlocking {
anotherClass.someOtherMethod()
callsProtectedMethod(normalizeValue)
}
}
private suspend fun callsProtectedMethod(value: String) = coroutineScope {
println("Original method")
returnDefaultString(value)
}
}
AnotherClass
class AnotherClass {
fun someOtherMethod(): Unit {
println("SomeOtherMethod original")
}
}
Test
class DefaultClassTest : FunSpec({
context("Testing DefaultClass") {
#MockK
lateinit var anotherClass: AnotherClass
#OverrideMockKs
lateinit var defaultClass: DefaultClass
beforeContainer {
MockKAnnotations.init(this)
}
test("testing mocks") {
defaultClass.testMethod("some method")
}
}
I've changed the initialization to beforeTest, taken it out of the context, and also use beforeContainer, beforeTest, beforeSpec, but none of these work... every time I still get lateinit property defaultClass has not been initialized
So, I recreated the same test using JUnit and I don't have this issue.
class DefaultClassJUnitTest {
companion object {
#MockK
lateinit var anotherClass: AnotherClass
#OverrideMockKs
lateinit var defaultClass: DefaultClass
#BeforeAll
#JvmStatic
fun setup() {
MockKAnnotations.init(this)
}
}
#Test
fun `Testing with JUnit`() {
every { anotherClass.someOtherMethod() } answers {
println("Mocking another class")
}
val value = defaultClass.testMethod("some method")
}
}
So I'm pretty sure that I'm doing something wrong when using Kotest. I hope anyone might help me, thanks...
I think MockK is probably not looking for variables defined within function scopes. If you want to use the annotations, you likely have to move them to the companion object, like this:
class DefaultClassTest : FunSpec({
context("Testing DefaultClass") {
beforeContainer {
MockKAnnotations.init(this)
}
test("testing mocks") {
defaultClass.testMethod("some method")
}
}
}) {
companion object {
#MockK
lateinit var anotherClass: AnotherClass
#OverrideMockKs
lateinit var defaultClass: DefaultClass
}
}

How can I use dependency injection in CoroutineWorker with hilt?

I'm learning dependency injection, the following Code A and Code B are from the project https://github.com/android/sunflower
The author has defined a dependency injection PlantDao in Code A, but a PlantDao object is created with code database.plantDao() manually in Code B.
Why doesn't the author use dependency injection with the object PlantDao in Code B? How can I use dependency injection with the object PlantDao in Code B?
Code A
#InstallIn(SingletonComponent::class)
#Module
class DatabaseModule {
#Singleton
#Provides
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase {
return AppDatabase.getInstance(context)
}
#Provides
fun providePlantDao(appDatabase: AppDatabase): PlantDao {
return appDatabase.plantDao()
}
...
}
Code B
class SeedDatabaseWorker(
context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
try {
val filename = inputData.getString(KEY_FILENAME)
if (filename != null) {
applicationContext.assets.open(filename).use { inputStream ->
JsonReader(inputStream.reader()).use { jsonReader ->
...
val database = AppDatabase.getInstance(applicationContext)
database.plantDao().insertAll(plantList)
...
} else {
...
}
} catch (ex: Exception) {
...
}
}
..
}
Added Content
To Andrew: Thanks!
In this question, you told me that #InstallIn(SingletonComponent::class) will be available to the entire application, you can see Image 1.
The author has defined a dependency injection object of PlantDao in Code A and install it as SingletonComponent::class.
So I think that the object of PlantDao will be available to the entire application,why can't I use the dependency injection object of PlantDao directly in Code B?
Image 1
Code D
class SeedDatabaseWorker #Inject constructor(
database: AppDatabase,
context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
try {
val filename = inputData.getString(KEY_FILENAME)
if (filename != null) {
applicationContext.assets.open(filename).use { inputStream ->
JsonReader(inputStream.reader()).use { jsonReader ->
...
database.plantDao().insertAll(plantList)
...
} else {
..
}
You have to annotate your worker with #HiltWorker, your context and params with #Assisted, your constructor with #AssistedInject and then you can constructor inject your dao.
WorkerCode
#HiltWorker
class SeedDatabaseWorker #AssistedInject constructor(
#Assisted context: Context,
#Assisted workerParams: WorkerParameters,
private val database: AppDatabase
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
try {
val filename = inputData.getString(KEY_FILENAME)
if (filename != null) {
applicationContext.assets.open(filename).use { inputStream ->
JsonReader(inputStream.reader()).use { jsonReader ->
...
database.plantDao().insertAll(plantList)
...
} else {
...
}
} catch (ex: Exception) {
...
}
}
..
}
Furthermore, you have to change the default WorkerFactory to a hiltWorkerFactory and remove the default initializer:
AppCode
#HiltAndroidApp
class ExampleApplication : Application(), Configuration.Provider {
#Inject lateinit var workerFactory: HiltWorkerFactory
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
}
AndroidManifest.xml
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove" />
Needed Dependencies
implementation 'androidx.hilt:hilt-work:1.0.0'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
implementation 'androidx.work:work-runtime-ktx:2.5.0'
Be aware that some processes change when later updating androidx.work to 2.6. You can read more here
Edit
You can use your dependency injection plantdao directly in code b. That's what private val database: AppDatabase inside your constructor means. In the first step, you provided your plantdao to hilt and told it how to create an instance of plantdao. In the next step (code b), you retain an instance of your plantdao via constructor injecting it. You have to first provide it to hilt (via a module) and then you can retain it (via constructor injection).

UninitializedPropertyAccessException using dagger 2

I am trying to inject an AlertDialog to MainActivity like
class MainActivity : BaseActivity() {
#Inject
val alertStore:AlertStore; //propery must be initialized or abstract Error
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Where as AppComponent.kt
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
fun addContext(#BindsInstance context: Context): Builder
fun build(): AppComponent
}
}
AppModule.kt
#Module
class AppModule {
#Singleton
#Provides
fun provideAlertStore(context: Context) : AlertStore = AlertStore(context)
}
App.kt
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().addContext(this).build()
}
}
I am new to dagger. So bear with me. I Think I am missing context . Suggest me and please explain a little bit So I understand this concept as well
Do I have to inject MainActivity as well to get Alert or my context is missing.
File Under build folder Generated
#kotlin.Metadata(mv = {1, 1, 15}, bv = {1, 0, 3}, k = 1, d1 = {"\u0000&\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0002J\u0012\u0010\t\u001a\u00020\n2\b\u0010\u000b\u001a\u0004\u0018\u00010\fH\u0014R\u0016\u0010\u0003\u001a\u00020\u00048\u0006X\u0087\u0004\u00a2\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006R\u000e\u0010\u0007\u001a\u00020\bX\u0082.\u00a2\u0006\u0002\n\u0000\u00a8\u0006\r"}, d2 = {"Lcom/example/myapplication/MainActivity;", "Lcom/example/myapplication/common/BaseActivity;", "()V", "alertStore", "Lcom/example/myapplication/common/AlertStore;", "getAlertStore", "()Lcom/example/myapplication/common/AlertStore;", "viewModel", "Lcom/example/myapplication/home/MainViewModel;", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "app_debug"})
public final class MainActivity extends com.example.myapplication.common.BaseActivity {
#org.jetbrains.annotations.NotNull()
#javax.inject.Inject()
private final com.example.myapplication.common.AlertStore alertStore = null;
private com.example.myapplication.home.MainViewModel viewModel;
private java.util.HashMap _$_findViewCache;
#org.jetbrains.annotations.NotNull()
public final com.example.myapplication.common.AlertStore getAlertStore() {
return null;
}
#java.lang.Override()
protected void onCreate(#org.jetbrains.annotations.Nullable()
android.os.Bundle savedInstanceState) {
}
public MainActivity() {
super();
}
}
EDIT after Alexey Suggestion
After updating my variable like
#Inject lateinit var alertStore: AlertStore
The error gone but on running app below error occur
Method threw 'kotlin.UninitializedPropertyAccessException' exception.
Your issue is not directly related to Dagger, but rather to Kotlin.
Sinc you use a val property, you need to initialize it with a value. Otherwise it would be uninitialized while the contract states that it's non-null. An abstract property obviously wouldn't work, so you might need to declare you property differently and inject with a setter.
#set:Inject internal lateinit var alertStore: AlertStore

lateinit property has not been initialized with dagger and coroutine

I'm trying MVVM, Dagger2, Retrofit and Coroutine. Now I have the problem that I can successfully inject a ProfileService into my activity, but not into my repository. I get a profileService lateinit property has not been initialized
//MainActivity
#Inject
lateinit var profileService: ProfileService //only for testing
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val getTheExpectedResult = profileService.hasValidName("asdffff") //true
profileViewModel.createProfile("ku") //throw the not initialized
}
The viewmodel calls the repository.
// profile repository
private fun getProfileRepository(userId: String = "", apiKey: String = ""): ProfileRepository {
return ProfileRepository(ApiFactory.getApi(userId, apiKey))
}
fun createProfile(name: String) {
scope.launch {
try {
val profile = getProfileRepository().createProfile(name)
profileLiveData.postValue(profile)
}
//...
In the repository I inject the profileService
class ProfileRepository(private val api: NIPApiInterface) {
#Inject
lateinit var profileService: ProfileService
suspend fun createProfile(name: String): ProfileResponse? {
if (!profileService.hasValidName(name)) { //throw the not initialized
//...
My unspectacular ProfileService
class ProfileService #Inject constructor() {
fun hasValidName(name: String): Boolean {
return name.length > 3
}
}
So that I don't post too much code, the following info. In my Application I seem to initialize everything correctly, because the Activity can access the ProfileService. Here are my Dagger configurations:
//AppComponent
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class
]
)
interface AppComponent : AndroidInjector<NIPApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance application: NIPApplication): AppComponent
}
}
The AppModule
#Module
class AppModule {
#Provides
#Singleton
fun provideContext(app: NIPApplication): Context = app
}
If you need more code, please send a comment.
Dagger won't inject dependencies unless it creates an instance of a class that needs injection or is explicitly asked to. Since the ProfileRepository object is created by you, it won't get its dependencies injected by Dagger.
The best way to solve this is to let Dagger create ProfileRepository objects by #Inject-annotated constructor (or using #Provides-annotated method in a Dagger module (provider), however this seems quite redundant in this particular case):
class ProfileRepository #Inject constructor(
private val api: NIPApiInterface,
private val profileService
)
Note that now Dagger will want to inject an NIPApiInterface object as well, so you have to create a provider for that or remove it from the constructor and pass it in some other way.

How to fix "[Dagger/MissingBinding]" in kotlin?

I'm trying to inject LatestChart in AppComponent and sloving issue with [Dagger/MissingBinding] LatestChart cannot be provided without an #Inject constructor or an #Provides-annotated method...
Thanks in advance for your help
LatestChart.kt
class LatestChart {
#Inject
lateinit var context: Context
#Inject
lateinit var formatter: YearValueFormatter
lateinit var chart: LineChart
init {
App.appComponent.inject(this)
}
fun initChart(chart: LineChart) {
this.chart = chart
***
}
fun addEntry(value: Float, date: Float) {
***
}
}
private fun createSet(): LineDataSet {
***
}
return set
}
}
And AppComponent.kt
#Component(modules = arrayOf(AppModule::class, RestModule::class, MvpModule::class, ChartModule::class))
#Singleton
interface AppComponent {
***
fun inject(chart: LatestChart)
}
Compilation error:
***AppComponent.java:8: error: [Dagger/MissingBinding] sn0w.test.crypto.chart.LatestChart cannot be provided without an #Inject constructor or an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
public abstract interface AppComponent {
^
A binding with matching key exists in component: sn0w.test.crypto.di.AppComponent
sn0w.test.crypto.chart.LatestChart is injected at
sn0w.test.crypto.activities.ChartActivity.latestChart
sn0w.test.crypto.activities.ChartActivity is injected at
sn0w.test.crypto.di.AppComponent.inject(sn0w.test.crypto.activities.ChartActivity)
The way you are currently trying to inject dependencies into the LatestChart is how you satisfy the dependencies in objects that won't be created by Dagger (e.g. activities). If you create the LatestChart object by yourself (just val latestCharts = LatestCharts()), you'll see that context and formatter are actually injected into this newly created instance.
However if you want Dagger to inject a LatestChart object into another (into ChartActivity I assume based on the compilation error), you have to let Dagger know how to create its instances. There are 2 ways to achieve that:
1. Annotate the LatestChart constructor with #Inject
class LatestChart #Inject constructor(
private val context: Context,
private val formatter: YearValueFormatter
) {
lateinit var chart: LineChart
fun initChart(chart: LineChart) { ... }
fun addEntry(value: Float, date: Float) { ... }
private fun createSet(): LineDataSet { ... }
}
2. Create a provider method in one of Dagger modules
class LatestChart(private val context: Context, private val formatter: YearValueFormatter) {
lateinit var chart: LineChart
fun initChart(chart: LineChart) { ... }
fun addEntry(value: Float, date: Float) { ... }
private fun createSet(): LineDataSet { ... }
}
#Module
class LatestChartModule {
#Module
companion object {
#JvmStatic
#Provides
fun provideLatestChart(context: Context, formatter: YearValueFormatter): LatestChart =
LatestChart(context, formatter)
}
}