2
votes

I am new to dagger 2 and kotlin both. Getting lateinit property not initialized.

I have a module which have few @Provides methods but one of the class not able to create object which used @Inject and lateinit.

Login service takes "LoginAPI" as parameter and works fine but as i want all my login related API's to use the same service. There is one more API related "LoginWithOrgAPI".

Now my need is to get any API object when needed in the LoginService class. So i tries using lateinit with @Inject as show in LoginService class but its not working.

@Module(includes = [(NetworkingModule::class)])

class LoginModule {

    @Provides
    fun provideLoginApi(retrofit: Retrofit): LoginApi =
            retrofit.create(LoginApi::class.java)

    @Provides
    fun provideLoginWithOrgApi(retrofit: Retrofit): LoginWithOrgApi =
            retrofit.create(LoginWithOrgApi::class.java)

    @Provides
    fun provideLoginService(api: LoginApi): LoginService =
            LoginService(api)

    @Provides
    fun provideLoginInteractor(apiService: LoginService): LoginInteractor =
            LoginInteractor(apiService)
}

// adding LoginService class

class LoginService(val loginAPI: LoginApi) {

    @Inject
    lateinit var loginWithOrgApi: LoginWithOrgApi


    fun loginAPIService(user: String, password: String?, extension: String, otp: String?,
                       hardwareId: String): Single<LoginAPIResponseData> {
        password?.let {

            return loginAPI.getLogin(user, it, extension, null, hardwareId)
        }?: run {
            return loginAPI.getLogin(user, null, extension, otp, hardwareId)
        }
    }

    fun loginWithOrg(user: String, password: String?, extension: String, otp: String?,
                     userId: String, hardwareId: String): Single<LoginAPIResponseData>{
        password?.let {

            return loginWithOrgApi.getLogin(user, it, extension, null, userId, hardwareId)
        }?: run {
            return loginWithOrgApi.getLogin(user, null, extension, otp, userId, hardwareId)
        }
    }
}

// component

@Component(modules = [(LoginModule::class)])

    interface LoginComponent {

           fun loginInteractor(): LoginInteractor

    }

// api interface

interface LoginWithOrgApi {

    @POST("access/v1/login/")
      @FormUrlEncoded
      fun getLogin(@Field("user") user: String,
                   @Field("password") password: String?,
                   @Field("mobile_extension") extension: String,
                   @Field("otp") otp: String?,
                   @Field("user_id") userId: String,
                   @Field("hardware_id") hardwareId: String): Single<LoginAPIResponseData>

}

Getting the crash saying "lateinit" property not initialized when trying to call method "loginWithOrg"

My understanding is that once define and provided through module, i can get the object through @Inject in the dependency graph but something is missing here.

// my objective for LoginService class

class LoginService() {

    @Inject
    var loginWithOrgApi: LoginWithOrgApi 


@Inject
        var loginApi: LoginApi



    fun loginAPIService(user: String, password: String?, extension: String, otp: String?,
                       hardwareId: String): Single<LoginAPIResponseData> {
        password?.let {

            return loginAPI.getLogin(user, it, extension, null, hardwareId)
        }?: run {
            return loginAPI.getLogin(user, null, extension, otp, hardwareId)
        }
    }

    fun loginWithOrg(user: String, password: String?, extension: String, otp: String?,
                     userId: String, hardwareId: String): Single<LoginAPIResponseData>{
        password?.let {

            return loginWithOrgApi.getLogin(user, it, extension, null, userId, hardwareId)
        }?: run {
            return loginWithOrgApi.getLogin(user, null, extension, otp, userId, hardwareId)
        }
    }
}
1
I see you creating the object (LoginService(api)), but I don't see you injecting it&mdash;hence the fields won't be set. You should have a look at constructor injection which would reduce the boilerplate and fix your issueDavid Medenjak
Thanks for the reply. There are two API's in the module class, LoginAPI and LoginWithOrgAPI. LoginAPI is passed in the service class inside constructor and is working fine but the other one i'm trying to get it through @Inject which i want to use in the second function written. That particular one is not working.sandeep_gautam
do you mean i should remove the constructor parameter and try to inject both the API's through @Inject annotation?sandeep_gautam
Hope you have mentioned your class(In which you want to inject the object) in the component interface of the dagger.Uttam Panchasara
Please paste all codeUttam Panchasara

1 Answers

1
votes

Because you are mixing two independent things: member injection via void inject(MyClass myClass); and constructor injection.

In fact, you even have a separate field that "ought to be member-injected", even though you could technically receive that as a constructor param?

class LoginService(val loginAPI: LoginApi) { // <-- constructor param + field

    @Inject
    lateinit var loginWithOrgApi: LoginWithOrgApi // <-- why is this a lateinit member injected field? 
    // this could also be constructor param

So it should be as follows.

@Module(includes = [(NetworkingModule::class)])

class LoginModule {

    @Provides
    fun loginApi(retrofit: Retrofit): LoginApi =
            retrofit.create(LoginApi::class.java)

    @Provides
    fun loginWithOrgApi(retrofit: Retrofit): LoginWithOrgApi =
            retrofit.create(LoginWithOrgApi::class.java)

    //@Provides
    //fun provideLoginService(api: LoginApi): LoginService =
    //        LoginService(api)

    //@Provides
    //fun provideLoginInteractor(apiService: LoginService): LoginInteractor =
    //        LoginInteractor(apiService)
}

and

class LoginService @Inject constructor(
    val loginAPI: LoginApi, 
    val loginWithOrgApi: LoginWithOrgApi
) {
    fun loginAPIService(user: String, password: String?, extension: String, otp: String?,
                       hardwareId: String): Single<LoginAPIResponseData> {
        password?.let { password ->
            return loginAPI.getLogin(user, password, extension, null, hardwareId)
        }?: run {
            return loginAPI.getLogin(user, null, extension, otp, hardwareId)
        }
    }

    fun loginWithOrg(user: String, password: String?, extension: String, otp: String?,
                     userId: String, hardwareId: String): Single<LoginAPIResponseData>{
        password?.let { password ->
            return loginWithOrgApi.getLogin(user, password, extension, null, userId, hardwareId)
        }?: run {
            return loginWithOrgApi.getLogin(user, null, extension, otp, userId, hardwareId)
        }
    }
}

and

class LoginInteractor @Inject constructor(
    val apiService: LoginService
) {
    ...
}

You own those classes, so there is no reason to use @field:Inject lateinit var + void inject(MyClass myClass); here.