-4

Hi I am trying to inject retrofit in ApiHelperImpl.kt class. I have below module and component.

AppComponent.kt

@Component(modules = arrayOf(AndroidInjectionModule::class, AppModule::class, ActivityBuilder::class))
@Singleton
interface AppComponent {
    fun inject(app: PartnerApplication)
}

AppModule.kt

@Module
class AppModule {

    @Provides
    @Singleton
    fun providesPartnerApplication(application: Application): Application = application

    @Provides
    @Singleton
    fun providesSharedPreferences(application: Application): SharedPreferences {
        return PreferenceManager.getDefaultSharedPreferences(application)
    }

    @Provides
    @Singleton
    fun provideOkHttpCache(application: Application): Cache {
        val cacheSize = 10 * 1024 * 1024L // 10 MiB
        return Cache(application.cacheDir, cacheSize)
    }

    @Provides
    @Singleton
    fun provideMoshi(): Moshi = Moshi.Builder().build()

    @Provides
    @Singleton
    fun provideOkHttpClient(cache: Cache): OkHttpClient {
        val okHttpClient = OkHttpClient()
        okHttpClient.newBuilder()
                .cache(cache)
                .build()
        return okHttpClient
    }

    @Provides
    @Singleton
    fun provideRetrofit(moshi: Moshi, okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
                .addConverterFactory(MoshiConverterFactory.create(moshi))
                .baseUrl(BuildConfig.BASE_URL)
                .client(okHttpClient)
                .build()
    }

}

PartnerApplication.kt

class PartnerApplication : Application(), AnkoLogger, HasActivityInjector {

    @Inject lateinit var activityInjector: DispatchingAndroidInjector<Activity>

    override fun activityInjector(): AndroidInjector<Activity> {
        return activityInjector
    }

    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.create().inject(this)
    }

    override protected fun attachBaseContext(base: Context) {
        super.attachBaseContext(base)
        MultiDex.install(this)
    }

}

ApiHelper.kt

interface ApiHelper {
    fun doServerLoginApiCall(email: String, password: String): Observable<LoginResponse>
    fun doServerRegistrationApiCall(): Observable<RegistrationResponse>
}

ApiHelperImpl.kt

class ApiHelperImpl : ApiHelper {

    @Inject
    lateinit var retrofit: Retrofit

    override fun doServerLoginApiCall(email: String, password: String): Observable<LoginResponse> {
        return retrofit.create(RestApi::class.java).login(email, password)
    }

    override fun doServerRegistrationApiCall(): Observable<RegistrationResponse> {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}

LoginActivity.kt

class LoginActivity : BaseActivity() {

    @Inject
    lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        performDependencyInjection()
        super.onCreate(savedInstanceState)
        val activityLoginBinding: ActivityLoginBinding = DataBindingUtil.setContentView<ActivityLoginBinding>(this, R.layout.activity_login)
        activityLoginBinding.loginViewModel = loginViewModel
    }
}

LoginViewModel.kt

class LoginViewModel : ViewModel(), AnkoLogger {

    val emailField = ObservableField<String>()

    private val email: String
        get() = emailField.get()

    val passwordField = ObservableField<String>()

    private val password: String
        get() = passwordField.get()

    val progressVisibility: ObservableInt = ObservableInt(View.GONE)

    @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
    fun login(view: View) {
        if (isEmailAndPasswordValid(email, password))
            ApiHelperImpl().doServerLoginApiCall(email, password)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribeWith(object : CallbackWrapper<LoginResponse>() {

                        override fun onSuccess(loginResponse: LoginResponse) {
                        }
                    })

    }

    /**
     * Validate email and password. It checks email and password is empty or not
     * and validate email address is correct or not
     * @param email email address for login
     * @param password password for login
     * @return true if email and password pass all conditions else false
     */
    private fun isEmailAndPasswordValid(email: String, password: String): Boolean {

        if (email.isEmpty()) return false

        if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) return false

        if (password.isEmpty()) return false

        return true
    }

}

LoginActivityModule.kt

@Module
class LoginActivityModule {

    @Provides
    fun providesLoginActivityViewModel(): LoginViewModel {
        return LoginViewModel()
    }
}

ActivityBuilder.kt

@Module
abstract class ActivityBuilder {

    @ContributesAndroidInjector(modules = arrayOf(LoginActivityModule::class))
    abstract fun bindLoginActivity(): LoginActivity

}

I am getting an error

Process: com.partner.android, PID: 9697
kotlin.UninitializedPropertyAccessException: lateinit property retrofit has not been initialized
    at com.partner.android.data.remote.ApiHelperImpl.doServerLoginApiCall(ApiHelperImpl.kt:32)
    at com.partner.android.login.LoginViewModel.login(LoginViewModel.kt:45)
    at com.partner.android.databinding.ActivityLoginBinding$OnClickListenerImpl.onClick(ActivityLoginBinding.java:298)
    at android.view.View.performClick(View.java:5637)
    at android.view.View$PerformClick.run(View.java:22429)
    at android.os.Handler.handleCallback(Handler.java:751)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6119)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

Why retrofit is not injecting in ApiHelperImpl class.

N Sharma
  • 33,489
  • 95
  • 256
  • 444

2 Answers2

2

You can implement the same using module

@Module
class ApiModule {


@Provides
@Singleton
fun apiService(context: Context): WebService {
    val mBaseUrl = context.getString(if (BuildConfig.DEBUG) R.string.local_url else R.string.live_url)

    val loggingInterceptor = HttpLoggingInterceptor()
    loggingInterceptor.level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE

    val okHttpClient = OkHttpClient.Builder()
            .readTimeout(120, TimeUnit.SECONDS)
            .writeTimeout(120, TimeUnit.SECONDS)
            .connectTimeout(120, TimeUnit.SECONDS)
            .addInterceptor(loggingInterceptor)
            //.addNetworkInterceptor(networkInterceptor)
            .build()

    return Retrofit.Builder().baseUrl(mBaseUrl)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build().create(WebService::class.java)

}

}

Also change in AppComponent

@Singleton
@Component(modules = arrayOf(ApplicationModule::class, ApiModule::class))
interface AppComponent {
...

fun apiService(): WebService

...
}

your changes

@Component(modules = arrayOf(AndroidInjectionModule::class, AppModule::class, ApiModule ::class))

add APIModule in relevant sub modules also

UPDATE

@Component(modules = arrayOf(AndroidInjectionModule::class, AppModule::class, ActivityBuilder::class))
@Singleton
interface AppComponent {
fun inject(app: PartnerApplication)
fun apiService(): ApiHelper 
}

and then inject whenever you want to use.

@Inject
var webService: WebService? = null
Aks4125
  • 4,522
  • 4
  • 32
  • 48
  • I have two methods as of now in `doServerLoginApiCall` which returns `Observable` and `doServerRegistrationApiCall` which returns `Observable` in `ApiHelperImpl`. I couldn't understand what changes I have to make in my code. can you show with few more line of code thank you – N Sharma Nov 14 '17 at 05:21
  • I updated my question, can you please check it again – N Sharma Nov 14 '17 at 05:45
  • What is `WebService` and how to inject `apiService()` – N Sharma Nov 14 '17 at 07:03
  • WebService is your interface class where your GET,POST methods are declared. Use `ApiHelper ` instead of WebService. and inject in your ApiHelper Implementation. – Aks4125 Nov 14 '17 at 07:45
  • Inject where in ApiHelper? Constructor ? – N Sharma Nov 14 '17 at 08:04
  • Thank you so much. I will try your solution. – N Sharma Nov 14 '17 at 09:33
  • Problem is not to initialize ApiHelperImpl. I am doing it like `ApiHelperImpl()` but it crashes when it tries to use `retrofit` there. it is not getting initialized, though I injected it there – N Sharma Nov 14 '17 at 14:30
  • @Williams I suggest you to go for dagger 2.10 implementation because for dagger 2.11 & 2.12, you won't be able to find proper documentation in Kotlin. Still, I tried but I will implement my own then only I can provide a proper solution. – Aks4125 Nov 14 '17 at 15:48
1

in order to use @Inject in ApiHelperImpl you have to add it (calling inject on the component) to the dependency graph. In this case I would pass it as dependency to the constructor of ApiHelperImpl

  class ApiHelperImpl @Inject constructor(val retrofit : Retrofit) : ApiHelper {

     val mRetrofit = retrofit

should do it

Blackbelt
  • 156,034
  • 29
  • 297
  • 305
  • I am calling it `ApiHelperImpl` from the `LoginViewModel` what to pass here ? like `ApiHelperImpl().doServerLoginApiCall(email, password)` – N Sharma Nov 06 '17 at 09:24
  • Do you mean this `class LoginViewModel @Inject constructor(val retrofit: Retrofit) : AnkoLogger {` – N Sharma Nov 06 '17 at 09:28
  • check [this](https://github.com/bblackbelt/BreweryApp/tree/develop/app/src/main/java/com/blackbelt/brewery/di) out – Blackbelt Nov 14 '17 at 14:31
  • Thanks. I am getting this error: dagger.internal.codegen.ComponentProcessor was unable to process this interface because not all of its dependencies could be resolved – N Sharma Nov 14 '17 at 18:53
  • e: public abstract interface AppComponent { e: ^ – N Sharma Nov 15 '17 at 14:35
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/159073/discussion-between-williams-and-blackbelt). – N Sharma Nov 15 '17 at 15:09
  • here you can see https://chat.stackoverflow.com/transcript/message/40043912#40043912 – N Sharma Nov 15 '17 at 15:37
  • Did you get a chance to look – N Sharma Nov 16 '17 at 16:45
  • It worked, I made one variable private, so it was the issue. Now I want to create instance of `ApiHelperImpl` to call `doServerLoginApiCall` how should i do in loginviewmodel – N Sharma Nov 16 '17 at 17:30
  • Ok thank you will try today, btw why it is constructor injection here ? – N Sharma Nov 17 '17 at 05:31
  • Well, I guess you want dagger to instante the VM for you. When you inject it, Android will be resolve the dependency(ies) for you. If VM is part of the life cycle extensions, then you are probably using the ViewModelProvider from the sdk. If that's the case you will need a custom factory. I'll provide an example in few hours if you need it – Blackbelt Nov 17 '17 at 05:44
  • I am doing it like this in `LoginActivity ` class `private val loginViewModel: LoginViewModel by lazy { ViewModelProviders.of(this).get(LoginViewModel::class.java) }` so here it will inject all dependencies which are in the constructor of loginviewmodel right ? – N Sharma Nov 17 '17 at 06:16
  • ok. just for learning what changes I need to make if I want to inject like this `@Inject lateinit var apiHelper : ApiHelper` ? in loginviewmodel – N Sharma Nov 17 '17 at 09:47
  • to inject that one in your Activity, you don't have to change anything. – Blackbelt Nov 17 '17 at 10:48
  • something like [this](https://gist.github.com/bblackbelt/8cd5ec78ab2a5150a1f72606ce22a365) should do it – Blackbelt Nov 17 '17 at 13:50
  • I tried suggested solution, I am getting this error https://pastebin.com/FvdhJSYL can you look please – N Sharma Nov 18 '17 at 09:10
  • As usual some code would help. Btw yesterday I pushed some VMs on the same repository. Very close to what your are trying right now. Give it a try! – Blackbelt Nov 18 '17 at 09:13
  • Thank you so much. It worked :) I am getting this error https://pastebin.com/aqLVkMby and code here https://pastebin.com/QhJH1Gbr can you have a look pls – N Sharma Nov 18 '17 at 17:32
  • 1
    `ViewModelProviders.of(this).get(LoginViewModel::class.java)` should be `ViewModelProviders.of(this, mFactory).get(LoginViewModel::class.java)` – Blackbelt Nov 18 '17 at 17:34