Kotlin, RxJava, Retrofit, Dagger 2 and MVP architecture sample
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.Singletonprivate 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.Queryinterface 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.Injectclass 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.Presenteroverride 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)}