3

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)
        }
    }
}
  • I see you creating the object (`LoginService(api)`), but I don't see you injecting it—hence the fields won't be set. You should have a look at [constructor injection](https://stackoverflow.com/a/50270376/1837367) which would reduce the boilerplate and fix your issue – David Medenjak Jan 21 '19 at 11:48
  • 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 Jan 21 '19 at 11:54
  • do you mean i should remove the constructor parameter and try to inject both the API's through @Inject annotation? – sandeep_gautam Jan 21 '19 at 12:04
  • Hope you have mentioned your class(In which you want to inject the object) in the component interface of the dagger. – Uttam Panchasara Jan 21 '19 at 12:19
  • Please paste all code – Uttam Panchasara Jan 21 '19 at 12:46
  • added more code, which shows all the links – sandeep_gautam Jan 21 '19 at 12:54

1 Answers1

1

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.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • Thank you for the answer. I'll try this and will let you know about the result. Actually as there are two login API's in my service class which i need depending on the situation. As i was planning my code my objective was to remove constructor param and later instantiate the API which is really required through **lazy** initialization but actually didn't knew how to go about it. – sandeep_gautam Jan 21 '19 at 13:12
  • Another question just in case i have 5-10 API's in a module and want to have single service class only, Then how to go about it. Having constructor params won't suit. Editted my question with the objective as well. – sandeep_gautam Jan 21 '19 at 13:20
  • It worked, thanks. How mixing both constructor injection and field injection led to the crash? In theory it was looking fine to me. – sandeep_gautam Jan 21 '19 at 13:34
  • 1
    You didn't ever invoke `component.inject(injectionTarget)` and you also didn't have a `@Inject constructor` therefore the class was never injected. You might also want to consider not making a new interface per each REST API call, you only need that if you have different baseUrl or different okClient. – EpicPandaForce Jan 21 '19 at 13:50
  • If you want to lazy-inject something, then just get `@Inject constructor(val lazyService: dagger.Lazy...)` – EpicPandaForce Jan 21 '19 at 13:52