From 8356d91a91820b13e4fb8e777e3f5054a451ea6e Mon Sep 17 00:00:00 2001 From: juan ocampo Date: Sat, 4 May 2019 19:38:44 -0500 Subject: [PATCH 1/3] Migrating Coroutines to RX java --- app/build.gradle | 14 ++-- .../mytaxy/test/model/IRepository.kt | 3 +- .../mytaxy/test/model/Repository.kt | 51 ++++-------- .../mytaxy/test/model/di/RepositoryModule.kt | 3 + .../model/source/remote/IRemoteDataSource.kt | 3 +- .../model/source/remote/RemoteDataSource.kt | 11 +-- .../model/source/remote/mapper/IMapper.kt | 5 -- .../model/source/remote/mapper/TaxiMapper.kt | 10 ++- .../model/source/remote/service/TaxiApi.kt | 4 +- .../mytaxy/test/viewmodel/TaxiViewModel.kt | 82 ++++++++----------- 10 files changed, 82 insertions(+), 104 deletions(-) delete mode 100644 app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/mapper/IMapper.kt diff --git a/app/build.gradle b/app/build.gradle index 8f43028..4cf50ed 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,6 +27,9 @@ dependencies { def lifecycle_version = "1.1.1" def daggerVersion = '2.19' def kotlinCoroutineVersion = '1.0.1' + def rxAndroidVersion = '2.1.0' + def rxJavaVersion = '2.2.3' + def retrofitVersion = '2.5.0' implementation fileTree(include: ['*.jar'], dir: 'libs') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" @@ -40,12 +43,13 @@ dependencies { annotationProcessor "android.arch.persistence.room:compiler:$room_version" // Retrofit - implementation 'com.squareup.retrofit2:retrofit:2.5.0' - implementation 'com.squareup.retrofit2:converter-gson:2.5.0' + implementation "com.squareup.retrofit2:retrofit:${retrofitVersion}" + implementation "com.squareup.retrofit2:converter-gson:${retrofitVersion}" - //coroutines - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutineVersion" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutineVersion" + //RX + implementation "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}" + implementation "io.reactivex.rxjava2:rxjava:${rxJavaVersion}" + implementation "com.squareup.retrofit2:adapter-rxjava2:${retrofitVersion}" // Dagger 2 implementation "com.google.dagger:dagger:$daggerVersion" diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/model/IRepository.kt b/app/src/main/java/com/juanocampo/mytaxy/test/model/IRepository.kt index 624f1d7..052e6ef 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/model/IRepository.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/model/IRepository.kt @@ -2,9 +2,10 @@ package com.juanocampo.mytaxy.test.model import com.juanocampo.mytaxy.test.model.domain.Resource import com.juanocampo.mytaxy.test.model.domain.Taxi +import io.reactivex.Observable interface IRepository { - suspend fun requestTaxisByLocation(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double): Resource> + fun requestTaxisByLocation(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double): Observable>> } \ No newline at end of file diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/model/Repository.kt b/app/src/main/java/com/juanocampo/mytaxy/test/model/Repository.kt index e70dd75..9e284a3 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/model/Repository.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/model/Repository.kt @@ -4,47 +4,30 @@ import com.juanocampo.mytaxy.test.model.domain.Resource import com.juanocampo.mytaxy.test.model.domain.Taxi import com.juanocampo.mytaxy.test.model.source.remote.IRemoteDataSource import com.juanocampo.mytaxy.test.model.source.remote.mapper.TaxiMapper -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import io.reactivex.Observable class Repository( private val iRemoteDataSource: IRemoteDataSource, - private val iMapper: TaxiMapper, - private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO + private val taxiResponseMapper: TaxiMapper ) : IRepository { - override suspend fun requestTaxisByLocation( + override fun requestTaxisByLocation( p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double - ): Resource> { - return withContext(ioDispatcher) { - return@withContext fetchTaxisFromRemote( - p1Lat, - p1Lon, - p2Lat, - p2Lon - ) - } - } + ): Observable>> { - private fun fetchTaxisFromRemote( - p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double - ): Resource> { - return try { - val fetchedItems = iRemoteDataSource.fetchTaxisByLocation( - p1Lat, - p1Lon, - p2Lat, - p2Lon - ) - if (fetchedItems.isNullOrEmpty()) { - Resource.error("could not load info, try later") - } else { - val mappedItems = iMapper.mapResponseToAppModel(fetchedItems) - Resource.success(mappedItems) - } - } catch (e: Exception) { - Resource.error(e.message ?: "Something went wrong ") + return iRemoteDataSource.fetchTaxisByLocation( + p1Lat, + p1Lon, + p2Lat, + p2Lon + ).toObservable() + .map(taxiResponseMapper) + .map {listItems -> + return@map if (listItems.isNullOrEmpty()) { + Resource.error("could not load info, try later") + } else { + Resource.success(listItems) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/model/di/RepositoryModule.kt b/app/src/main/java/com/juanocampo/mytaxy/test/model/di/RepositoryModule.kt index 2234ba3..ce533a0 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/model/di/RepositoryModule.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/model/di/RepositoryModule.kt @@ -10,6 +10,7 @@ import com.juanocampo.mytaxy.test.model.source.remote.service.TaxiApi import dagger.Module import dagger.Provides import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory @@ -22,6 +23,8 @@ class RepositoryModule { val builder: Retrofit.Builder = Retrofit.Builder() .baseUrl("https://fake-poi-api.mytaxi.com/") .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + val retrofit = builder.build() return retrofit.create(TaxiApi::class.java) diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/IRemoteDataSource.kt b/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/IRemoteDataSource.kt index ebff057..dfef906 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/IRemoteDataSource.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/IRemoteDataSource.kt @@ -1,8 +1,9 @@ package com.juanocampo.mytaxy.test.model.source.remote import com.juanocampo.mytaxy.test.model.source.remote.domain.TaxiResponse +import io.reactivex.Single interface IRemoteDataSource { - fun fetchTaxisByLocation(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double): List + fun fetchTaxisByLocation(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double):Single> } \ No newline at end of file diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/RemoteDataSource.kt b/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/RemoteDataSource.kt index 973f1ca..d43a71a 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/RemoteDataSource.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/RemoteDataSource.kt @@ -2,17 +2,14 @@ package com.juanocampo.mytaxy.test.model.source.remote import com.juanocampo.mytaxy.test.model.source.remote.domain.TaxiResponse import com.juanocampo.mytaxy.test.model.source.remote.service.TaxiApi +import io.reactivex.Single class RemoteDataSource(private val api: TaxiApi): IRemoteDataSource { - override fun fetchTaxisByLocation(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double): List { - val request = api.fetchTaxisByLocation(p1Lat = p1Lat.toString(), p1Lon = p1Lon.toString(), p2Lat = p2Lat.toString(), p2Lon = p2Lon.toString()) - val apiResponse = request.execute() - return if (apiResponse.isSuccessful && apiResponse.body()?.poiList != null) { - apiResponse.body()?.poiList!! - } else { - emptyList() + override fun fetchTaxisByLocation(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double): Single> { + return api.fetchTaxisByLocation(p1Lat = p1Lat.toString(), p1Lon = p1Lon.toString(), p2Lat = p2Lat.toString(), p2Lon = p2Lon.toString()).map { + return@map it.poiList ?: emptyList() } } diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/mapper/IMapper.kt b/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/mapper/IMapper.kt deleted file mode 100644 index 2407cf9..0000000 --- a/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/mapper/IMapper.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.juanocampo.mytaxy.test.model.source.remote.mapper - -interface IMapper { - fun mapResponseToAppModel(toParse: R) : T -} \ No newline at end of file diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/mapper/TaxiMapper.kt b/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/mapper/TaxiMapper.kt index b80160b..825dda9 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/mapper/TaxiMapper.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/mapper/TaxiMapper.kt @@ -2,14 +2,20 @@ package com.juanocampo.mytaxy.test.model.source.remote.mapper import com.juanocampo.mytaxy.test.model.domain.Taxi import com.juanocampo.mytaxy.test.model.source.remote.domain.TaxiResponse +import io.reactivex.functions.Function -class TaxiMapper: IMapper, List> { +class TaxiMapper: Function, List> { - override fun mapResponseToAppModel(toParse: List): List { + + override fun apply(toParse: List): List { + if (toParse.isNullOrEmpty()) { + return emptyList() + } val list = ArrayList() toParse.forEach { list.add(it.toTaxi()) } return list } + } \ No newline at end of file diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/service/TaxiApi.kt b/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/service/TaxiApi.kt index 450bdc6..808fc9c 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/service/TaxiApi.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/service/TaxiApi.kt @@ -1,7 +1,7 @@ package com.juanocampo.mytaxy.test.model.source.remote.service import com.juanocampo.mytaxy.test.model.source.remote.domain.TaxiApiResponse -import retrofit2.Call +import io.reactivex.Single import retrofit2.http.GET import retrofit2.http.Query @@ -11,5 +11,5 @@ interface TaxiApi { fun fetchTaxisByLocation(@Query("p1Lat") p1Lat: String, @Query("p1Lon") p1Lon: String, @Query("p2Lat") p2Lat: String, - @Query("p2Lon") p2Lon: String): Call + @Query("p2Lon") p2Lon: String): Single } \ No newline at end of file diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/viewmodel/TaxiViewModel.kt b/app/src/main/java/com/juanocampo/mytaxy/test/viewmodel/TaxiViewModel.kt index 0ff43e5..30c8bc7 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/viewmodel/TaxiViewModel.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/viewmodel/TaxiViewModel.kt @@ -6,28 +6,26 @@ import android.arch.lifecycle.MutableLiveData import android.arch.lifecycle.Transformations import android.arch.lifecycle.ViewModel import android.support.annotation.UiThread -import android.support.annotation.WorkerThread import com.google.android.gms.maps.model.LatLng import com.juanocampo.mytaxy.test.model.IRepository import com.juanocampo.mytaxy.test.model.domain.Resource import com.juanocampo.mytaxy.test.model.domain.Status import com.juanocampo.mytaxy.test.model.domain.Taxi import com.juanocampo.mytaxy.test.utils.delegate.model.RecyclerViewType -import kotlinx.coroutines.* +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers -class TaxiViewModel(private val iRepository: IRepository, - private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main, - private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO): ViewModel() { +class TaxiViewModel(private val iRepository: IRepository) : ViewModel() { val errorLiveData = MutableLiveData() val taxiMapLiveData = MutableLiveData>() private val mapClickedLivedData = MutableLiveData() - private val itemCLickedLiveData = MutableLiveData() + private val itemCLickedLiveData = MutableLiveData() private val requestListFocusLiveData: LiveData private val requestMapFocusLiveData: LiveData - private var isLoading = false private val mapItems: HashMap = HashMap() init { @@ -40,54 +38,45 @@ class TaxiViewModel(private val iRepository: IRepository, }) } - fun fetchTaxisByLocationPage(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double) { - GlobalScope.launch(ioDispatcher) { - syncRepository(p1Lat, - p1Lon, - p2Lat, - p2Lon) - } - } + private var disposable: Disposable? = null - @Synchronized - @WorkerThread - private suspend fun syncRepository(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double) { - if (!isLoading) { - isLoading = true - - val response = iRepository.requestTaxisByLocation(p1Lat, - p1Lon, - p2Lat, - p2Lon) - when { - response.status == Status.SUCCESS -> { - handleSuccessCase(response) + fun fetchTaxisByLocationPage(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double) { + disposable = iRepository.requestTaxisByLocation( + p1Lat, + p1Lon, + p2Lat, + p2Lon + ).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe({ response -> + when { + response.status == Status.SUCCESS -> { + handleSuccessCase(response) + } + else -> { + handleErrorCase(response.message) + } } - response.status == Status.LOADING -> isLoading = true - else -> { - handleErrorCase(response) - } - } - } + }, { + handleErrorCase(it.message ?: "Something when wrong, please try later") + }) + } + + override fun onCleared() { + super.onCleared() + disposable?.dispose() } - @WorkerThread - private suspend fun handleSuccessCase(response: Resource>) { - isLoading = false + private fun handleSuccessCase(response: Resource>) { response.info?.let { addItemsAndNotify(it) } } - @WorkerThread - private suspend fun handleErrorCase(response: Resource>) { - isLoading = false - publishUIResults(errorLiveData, response.message) + private fun handleErrorCase(message: String) { + publishUIResults(errorLiveData, message) } - @WorkerThread - private suspend fun addItemsAndNotify(itemsToAdd: List) { + private fun addItemsAndNotify(itemsToAdd: List) { itemsToAdd.forEach { if (it is Taxi) { mapItems[it.latLong] = it @@ -97,16 +86,15 @@ class TaxiViewModel(private val iRepository: IRepository, } @UiThread - private suspend fun publishUIResults(liveData: MutableLiveData, data: T) { - withContext(mainDispatcher) { - liveData.value = data - } + private fun publishUIResults(liveData: MutableLiveData, data: T) { + liveData.value = data } @UiThread fun setClickedMarker(latLng: LatLng) { mapClickedLivedData.value = latLng } + @UiThread fun setClickedItem(taxi: Taxi) { itemCLickedLiveData.value = taxi From f89ea64737a216a8f3bcf220ccabe90e9b235034 Mon Sep 17 00:00:00 2001 From: juan ocampo Date: Sat, 4 May 2019 19:58:04 -0500 Subject: [PATCH 2/3] Migrating Coroutines to RX java --- .../mytaxy/test/model/IRepository.kt | 4 +- .../mytaxy/test/model/Repository.kt | 9 +++- .../test/view/fragment/TaxisListFragment.kt | 6 +-- .../mytaxy/test/viewmodel/TaxiViewModel.kt | 47 +++++++------------ 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/model/IRepository.kt b/app/src/main/java/com/juanocampo/mytaxy/test/model/IRepository.kt index 052e6ef..fd74744 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/model/IRepository.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/model/IRepository.kt @@ -1,11 +1,13 @@ package com.juanocampo.mytaxy.test.model +import com.google.android.gms.maps.model.LatLng import com.juanocampo.mytaxy.test.model.domain.Resource import com.juanocampo.mytaxy.test.model.domain.Taxi + import io.reactivex.Observable interface IRepository { - fun requestTaxisByLocation(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double): Observable>> + fun requestTaxisByLocation(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double): Observable>> } \ No newline at end of file diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/model/Repository.kt b/app/src/main/java/com/juanocampo/mytaxy/test/model/Repository.kt index 9e284a3..5efdf1b 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/model/Repository.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/model/Repository.kt @@ -1,5 +1,6 @@ package com.juanocampo.mytaxy.test.model +import com.google.android.gms.maps.model.LatLng import com.juanocampo.mytaxy.test.model.domain.Resource import com.juanocampo.mytaxy.test.model.domain.Taxi import com.juanocampo.mytaxy.test.model.source.remote.IRemoteDataSource @@ -13,7 +14,7 @@ class Repository( override fun requestTaxisByLocation( p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double - ): Observable>> { + ): Observable>> { return iRemoteDataSource.fetchTaxisByLocation( p1Lat, @@ -26,7 +27,11 @@ class Repository( return@map if (listItems.isNullOrEmpty()) { Resource.error("could not load info, try later") } else { - Resource.success(listItems) + val mapItems: HashMap = HashMap() + listItems.forEach { + mapItems[it.latLong] = it + } + Resource.success(mapItems) } } } diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/view/fragment/TaxisListFragment.kt b/app/src/main/java/com/juanocampo/mytaxy/test/view/fragment/TaxisListFragment.kt index 95d9056..6d5dc0d 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/view/fragment/TaxisListFragment.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/view/fragment/TaxisListFragment.kt @@ -6,7 +6,6 @@ import android.os.Bundle import android.support.v4.app.Fragment import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearSnapHelper -import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -14,6 +13,7 @@ import android.widget.Toast import com.juanocampo.mytaxy.test.R import com.juanocampo.mytaxy.test.di.AndroidInjectorUtils import com.juanocampo.mytaxy.test.model.domain.Taxi +import com.juanocampo.mytaxy.test.utils.delegate.model.RecyclerViewType import com.juanocampo.mytaxy.test.view.adapter.TaxiAdapter import com.juanocampo.mytaxy.test.view.adapter.TaxiDelegateAdapter import com.juanocampo.mytaxy.test.viewmodel.TaxiViewModel @@ -54,8 +54,8 @@ class TaxisListFragment: Fragment(), TaxiDelegateAdapter.OnItemListListener { Toast.makeText(context, error, Toast.LENGTH_SHORT).show() }) - viewModel.getRequestListObserver().observe(this, Observer { - taxList.smoothScrollToPosition(adapter.items.indexOf(it)) + viewModel.getRequestListObserver().observe(this, Observer {taxi -> + taxList.smoothScrollToPosition(adapter.items.indexOf(taxi as RecyclerViewType)) }) } diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/viewmodel/TaxiViewModel.kt b/app/src/main/java/com/juanocampo/mytaxy/test/viewmodel/TaxiViewModel.kt index 30c8bc7..a6b4231 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/viewmodel/TaxiViewModel.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/viewmodel/TaxiViewModel.kt @@ -8,10 +8,8 @@ import android.arch.lifecycle.ViewModel import android.support.annotation.UiThread import com.google.android.gms.maps.model.LatLng import com.juanocampo.mytaxy.test.model.IRepository -import com.juanocampo.mytaxy.test.model.domain.Resource import com.juanocampo.mytaxy.test.model.domain.Status import com.juanocampo.mytaxy.test.model.domain.Taxi -import com.juanocampo.mytaxy.test.utils.delegate.model.RecyclerViewType import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers @@ -19,14 +17,14 @@ import io.reactivex.schedulers.Schedulers class TaxiViewModel(private val iRepository: IRepository) : ViewModel() { val errorLiveData = MutableLiveData() - val taxiMapLiveData = MutableLiveData>() + val taxiMapLiveData = MutableLiveData>() private val mapClickedLivedData = MutableLiveData() private val itemCLickedLiveData = MutableLiveData() - private val requestListFocusLiveData: LiveData + private val requestListFocusLiveData: LiveData private val requestMapFocusLiveData: LiveData - private val mapItems: HashMap = HashMap() + private var mapItems: HashMap = HashMap() init { requestListFocusLiveData = Transformations.map(mapClickedLivedData, Function { @@ -46,19 +44,23 @@ class TaxiViewModel(private val iRepository: IRepository) : ViewModel() { p1Lon, p2Lat, p2Lon - ).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe({ response -> - when { - response.status == Status.SUCCESS -> { - handleSuccessCase(response) - } - else -> { - handleErrorCase(response.message) + ).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) + .subscribe({ response -> + when { + response.status == Status.SUCCESS -> { + response.info?.let { + mapItems = response.info + publishUIResults(taxiMapLiveData, mapItems) } } + else -> { + handleErrorCase(response.message) + } + } - }, { - handleErrorCase(it.message ?: "Something when wrong, please try later") - }) + }, { + handleErrorCase(it.message ?: "Something when wrong, please try later") + }) } override fun onCleared() { @@ -66,25 +68,10 @@ class TaxiViewModel(private val iRepository: IRepository) : ViewModel() { disposable?.dispose() } - private fun handleSuccessCase(response: Resource>) { - response.info?.let { - addItemsAndNotify(it) - } - } - private fun handleErrorCase(message: String) { publishUIResults(errorLiveData, message) } - private fun addItemsAndNotify(itemsToAdd: List) { - itemsToAdd.forEach { - if (it is Taxi) { - mapItems[it.latLong] = it - } - } - publishUIResults(taxiMapLiveData, mapItems) - } - @UiThread private fun publishUIResults(liveData: MutableLiveData, data: T) { liveData.value = data From a10cb13cd9377fc6cfa84e82e8f11f8d51b4269c Mon Sep 17 00:00:00 2001 From: juan ocampo Date: Mon, 6 May 2019 21:25:02 -0500 Subject: [PATCH 3/3] Adding unit test --- app/build.gradle | 3 + .../model/source/remote/RemoteDataSource.kt | 1 - .../mytaxy/test/model/RepositoryTest.kt | 67 +++++++++++++++++++ .../source/remote/RemoteDataSourceTest.kt | 67 +++++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 app/src/test/java/com/juanocampo/mytaxy/test/model/RepositoryTest.kt create mode 100644 app/src/test/java/com/juanocampo/mytaxy/test/model/source/remote/RemoteDataSourceTest.kt diff --git a/app/build.gradle b/app/build.gradle index 4cf50ed..40fa051 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,8 @@ dependencies { def rxAndroidVersion = '2.1.0' def rxJavaVersion = '2.2.3' def retrofitVersion = '2.5.0' + def mockitoVersion = '2.23.4' + implementation fileTree(include: ['*.jar'], dir: 'libs') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" @@ -58,6 +60,7 @@ dependencies { testImplementation 'junit:junit:4.12' + testImplementation "org.mockito:mockito-core:${mockitoVersion}" androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'com.android.support:design:28.0.0' diff --git a/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/RemoteDataSource.kt b/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/RemoteDataSource.kt index d43a71a..32cae70 100644 --- a/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/RemoteDataSource.kt +++ b/app/src/main/java/com/juanocampo/mytaxy/test/model/source/remote/RemoteDataSource.kt @@ -4,7 +4,6 @@ import com.juanocampo.mytaxy.test.model.source.remote.domain.TaxiResponse import com.juanocampo.mytaxy.test.model.source.remote.service.TaxiApi import io.reactivex.Single - class RemoteDataSource(private val api: TaxiApi): IRemoteDataSource { override fun fetchTaxisByLocation(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double): Single> { diff --git a/app/src/test/java/com/juanocampo/mytaxy/test/model/RepositoryTest.kt b/app/src/test/java/com/juanocampo/mytaxy/test/model/RepositoryTest.kt new file mode 100644 index 0000000..6872ff3 --- /dev/null +++ b/app/src/test/java/com/juanocampo/mytaxy/test/model/RepositoryTest.kt @@ -0,0 +1,67 @@ +package com.juanocampo.mytaxy.test.model + +import com.google.android.gms.maps.model.LatLng +import com.juanocampo.mytaxy.test.model.domain.Resource +import com.juanocampo.mytaxy.test.model.domain.Status +import com.juanocampo.mytaxy.test.model.domain.Taxi +import com.juanocampo.mytaxy.test.model.source.remote.IRemoteDataSource +import com.juanocampo.mytaxy.test.model.source.remote.domain.Coordinate +import com.juanocampo.mytaxy.test.model.source.remote.domain.TaxiResponse +import com.juanocampo.mytaxy.test.model.source.remote.mapper.TaxiMapper +import io.reactivex.Single +import io.reactivex.android.plugins.RxAndroidPlugins +import io.reactivex.functions.Predicate +import io.reactivex.observers.TestObserver +import io.reactivex.schedulers.Schedulers +import org.junit.Before +import org.junit.Test + +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +class RepositoryTest { + + @Mock + lateinit var iRemoteDataSource: IRemoteDataSource + + lateinit var taxiResponseMapper: TaxiMapper + + lateinit var repository: Repository + + @Before + fun setUp() { + RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() } + MockitoAnnotations.initMocks(this) + taxiResponseMapper = TaxiMapper() + repository = Repository(iRemoteDataSource, taxiResponseMapper) + } + + @Test + fun `requestTaxisByLocation with an empty list will return a resource error`() { + Mockito.`when`(iRemoteDataSource.fetchTaxisByLocation(0.0, 0.0, 0.0, 0.0)).thenReturn(Single.just(emptyList())) + val testObserver = TestObserver>>() + repository.requestTaxisByLocation(0.0, 0.0, 0.0, 0.0).subscribe(testObserver) + testObserver.assertValueAt(0, Predicate { return@Predicate it.status == Status.ERROR }) + testObserver.assertValueAt(0, Predicate { return@Predicate it.message == "could not load info, try later" }) + } + + + @Test + fun `requestTaxisByLocation with a valid response list will map a success`() { + val taxiResponseList = ArrayList() + val taxiItem = TaxiResponse(1, Coordinate(0.0,0.0)) + taxiResponseList.add(taxiItem) + Mockito.`when`(iRemoteDataSource.fetchTaxisByLocation(0.0, 0.0, 0.0, 0.0)).thenReturn(Single.just(taxiResponseList)) + val testObserver = TestObserver>>() + repository.requestTaxisByLocation(0.0, 0.0, 0.0, 0.0).subscribe(testObserver) + testObserver.assertValueAt(0, Predicate { return@Predicate it.status == Status.SUCCESS }) + testObserver.assertValueAt(0, Predicate { return@Predicate it.info?.size == 1 }) + testObserver.assertValueAt(0, Predicate { return@Predicate ArrayList(it.info?.entries)[0].key.longitude == taxiItem.coordinate?.longitude}) + testObserver.assertValueAt(0, Predicate { return@Predicate ArrayList(it.info?.entries)[0].key.latitude == taxiItem.coordinate?.latitude}) + + testObserver.assertValueAt(0, Predicate { return@Predicate ArrayList(it.info?.values)[0].id == taxiItem.id}) + testObserver.assertValueAt(0, Predicate { return@Predicate ArrayList(it.info?.values)[0].latLong.latitude == taxiItem?.coordinate?.latitude}) + testObserver.assertValueAt(0, Predicate { return@Predicate ArrayList(it.info?.values)[0].latLong.longitude == taxiItem.coordinate?.longitude}) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/juanocampo/mytaxy/test/model/source/remote/RemoteDataSourceTest.kt b/app/src/test/java/com/juanocampo/mytaxy/test/model/source/remote/RemoteDataSourceTest.kt new file mode 100644 index 0000000..d559255 --- /dev/null +++ b/app/src/test/java/com/juanocampo/mytaxy/test/model/source/remote/RemoteDataSourceTest.kt @@ -0,0 +1,67 @@ +package com.juanocampo.mytaxy.test.model.source.remote + +import com.juanocampo.mytaxy.test.model.source.remote.domain.TaxiApiResponse +import com.juanocampo.mytaxy.test.model.source.remote.domain.TaxiResponse +import com.juanocampo.mytaxy.test.model.source.remote.service.TaxiApi +import io.reactivex.Single +import io.reactivex.android.plugins.RxAndroidPlugins +import io.reactivex.functions.Predicate +import io.reactivex.observers.TestObserver +import io.reactivex.schedulers.Schedulers +import org.junit.Before +import org.junit.Test + +import org.junit.Rule +import org.junit.rules.ExpectedException +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever + +class RemoteDataSourceTest { + + @Mock + lateinit var taxiApi: TaxiApi + + lateinit var remoteDataSource: RemoteDataSource + + + @get:Rule + val exception: ExpectedException = ExpectedException.none() + + @Before + fun setUp() { + RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() } + MockitoAnnotations.initMocks(this) + remoteDataSource = RemoteDataSource(taxiApi) + } + + @Test + fun `fetchTaxisByLocation with an empty poiList list will return empty TaxiResponse List`() { + var taxiResponse = TaxiApiResponse(null) + whenever(taxiApi.fetchTaxisByLocation("0.0", "0.0", "0.0", "0.0")).thenReturn(Single.just(taxiResponse)) + val testObserver = TestObserver>() + + remoteDataSource.fetchTaxisByLocation(0.0, 0.0, 0.0, 0.0).toObservable().subscribe(testObserver) + + verify(taxiApi).fetchTaxisByLocation("0.0", "0.0", "0.0", "0.0") + testObserver.assertValueAt(0, Predicate { return@Predicate it.isNullOrEmpty() }) + testObserver.assertComplete() + } + + @Test + fun `fetchTaxisByLocation with an empty poiList list will return valid TaxiResponse List`() { + val poiList = ArrayList() + poiList.add(TaxiResponse(0, null)) + var taxiResponse = TaxiApiResponse(poiList) + + whenever(taxiApi.fetchTaxisByLocation("0.0", "0.0", "0.0", "0.0")).thenReturn(Single.just(taxiResponse)) + val testObserver = TestObserver>() + + remoteDataSource.fetchTaxisByLocation(0.0, 0.0, 0.0, 0.0).toObservable().subscribe(testObserver) + + verify(taxiApi).fetchTaxisByLocation("0.0", "0.0", "0.0", "0.0") + testObserver.assertValueAt(0, poiList) + testObserver.assertComplete() + } +} \ No newline at end of file