Kotlin, RxJava, Retrofit, Dagger 2 and MVP architecture sample

serap bercin
3 min readJan 7, 2018

--

Lets try to setup MVP architecture with Kotlin, RxJava, Retrofit, Dagger in app.

Retrofit: is a Rest Client. Retrofit depend on OkHttp.

Dagger: used for dependency injection.

RxJava: Reactive Extensions.

First of all..We need to add dependencies for Retrofit, RxJava, OkHttp in build.gradle file.

implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
implementation 'com.squareup.okhttp3:okhttp:3.9.0'
implementation 'com.squareup.okio:okio:1.13.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
implementation "io.reactivex.rxjava2:rxkotlin:$rxkotlinVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
kapt "com.google.dagger:dagger-android-processor:$daggerVersion" implementation "com.google.dagger:dagger-android:$daggerVersion" implementation "com.google.dagger:dagger-android
support:$daggerVersion"

We need to create Api Module for Retrofit Setup.

addConvertorFactory() I used gson converter for data serilazation but if you want, you can create custom convertor, use moshi library or etc.

addCallAdapterFactory() support service method return types. I added RxJava2CallAdapterFactory.create()

HttpLoggingInterceptor() provide http logs for response and request.

Lets start to create the api module!

import dagger.Module
import dagger.Provides
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton
private const val API_KEY = "apikey"@Module
object ApiModule {
@JvmStatic
@Singleton
@Provides
fun provideRetrofit(): Retrofit {
val okHttpBuilder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) {
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
okHttpBuilder.addInterceptor(httpLoggingInterceptor)
}
okHttpBuilder.addInterceptor({
val request = it.request()
val url = request.url().newBuilder()
.addQueryParameter(API_KEY, BuildConfig.API_KEY)
.build()
it.proceed(request.newBuilder().url(url).build())
})
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpBuilder.build())
.build()
}@JvmStatic
@Singleton
@Provides
fun provideAccountService(retrofit: Retrofit): AccountService =
retrofit.create(AccountService::class.java)
}

We need to make an api call so create Service interface. I use Observable.

import io.reactivex.Observable
import retrofit2.http.GET
import retrofit2.http.Query
interface AccountService {@GET("account/reset_pass")
fun resetPassword(@Query("email") email: String): Observable<String>
}

thats it! We can go on the Model Layer. I need to create interface for using Impl class.

import io.reactivex.Observableinterface ResetPasswordDataSource {
fun resetPassword(email: String): Observable<String>
}

After, create DataSourceImpl class.

import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
class ResetPasswordModelDataSourceImpl @Inject constructor(private val accountService: AccountService): ResetPasswordDataSource {override fun resetPassword(email: String): Observable<String> {
return accountService.resetPassword(email)
.subscribeOn(Schedulers.io())
.map { it }
}
}

I create the AccountDataModule and it needs to include ApiModule class.

@Module(includes = [(ApiModule::class)])
abstract class AccountDataModule {
@Binds
@Singleton
abstract fun bindResetPasswordDataSource(resetPasswordModel: ResetPasswordModel):ResetPasswordDataSource
}

Now, we can go on Presenter Layer..

Contract for View and Presenter interfaces…

interface ResetPasswordContract {interface View {fun reset()
fun showException(errorMessage: String)
fun showDialog()
fun hideDialog()
}interface Presenter {
fun register(email: String)
fun onDestroy()
}
}

Lets create the presenter class;

class ResetPasswordPresenter @Inject constructor(
private val resetPasswordDataSource: ResetPasswordDataSource,
private val view: ResetPasswordContract.View)
: ResetPasswordContract.Presenter {
private val compositeDisposable = CompositeDisposable()override fun register(email: String) {compositeDisposable.add(resetPasswordDataSource.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { view.showDialog() }
.doOnNext { view.hideDialog() }
.subscribe({ resetEmail() },
{ throwable -> showException(throwable) }))
}
private fun resetEmail() {
view.reset()
}
private fun showException(throwable: Throwable?) {
val error = throwable as HttpException
try {
view.showException(errorMessage = error.message())
//TODO need JsonParser for BackendException
} catch (e: IOException) {
logException(throwable)
}
}override fun onDestroy() {
compositeDisposable.dispose()
}
}

Lets start the View Layer after we created presenter and model layers. Activity implement ResetPasswordContract.View and inject ResetPasswordContract.Presenter. We should make sure the view like be a dummy.

class ResetPasswordEmailActivity : AppCompatActivity(), ResetPasswordContract.View {@Inject
lateinit var resetPasswordPresenter: ResetPasswordContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
@OnClick(R.id.reset_button)
fun onResetButton() {
resetPasswordPresenter.register(email)
}

override fun onDestroy() {
super.onDestroy()
resetPasswordPresenter.onDestroy()
}
override fun reset() {
}
override fun showException(throwable: String) {
}
override fun showDialog() {
}
override fun hideDialog() {
}
}

ActivitiesModule : This module include each activity through ContributesAndroidInjector.

@Module
abstract class ActivitiesModule {
@ActivityScope
@ContributesAndroidInjector(modules =[(ResetPasswordModule::class)])
abstract fun contributeResetPasswordEmailActivityInjector(): ResetPasswordEmailActivity
}

bind Presenter and View in ResetPasswordModule.

@Module
abstract class ResetPasswordModule {

@Binds
abstract fun bindPresenter(resetPasswordCase: ResetPasswordPresenter): ResetPasswordContract.Presenter

@Binds
abstract fun bindView(resetPasswordEmailActivity: ResetPasswordEmailActivity): ResetPasswordContract.View

}

ApplicationComponent class: Finally We can bind it with Builder.

@Singleton
@Component(modules = [AndroidInjectionModule::class, AppModule::class, ActivitiesModule::class, AccountDataModule::class])
interface ApplicationComponent {
@Component.Builder
interface Builder {
fun build(): ApplicationComponent
@BindsInstance
fun application(application: Application): Builder
}
fun inject(application: Application)}

--

--