Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ 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'
def mockitoVersion = '2.23.4'


implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
Expand All @@ -40,12 +45,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"
Expand All @@ -54,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'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +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 {

suspend fun requestTaxisByLocation(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double): Resource<List<Taxi>>
fun requestTaxisByLocation(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double): Observable<Resource<HashMap<LatLng, Taxi>>>
}
56 changes: 22 additions & 34 deletions app/src/main/java/com/juanocampo/mytaxy/test/model/Repository.kt
Original file line number Diff line number Diff line change
@@ -1,50 +1,38 @@
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
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<List<Taxi>> {
return withContext(ioDispatcher) {
return@withContext fetchTaxisFromRemote(
p1Lat,
p1Lon,
p2Lat,
p2Lon
)
}
}
): Observable<Resource<HashMap<LatLng, Taxi>>> {

private fun fetchTaxisFromRemote(
p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double
): Resource<List<Taxi>> {
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 {
val mapItems: HashMap<LatLng, Taxi> = HashMap()
listItems.forEach {
mapItems[it.latLong] = it
}
Resource.success(mapItems)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TaxiResponse>
fun fetchTaxisByLocation(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double):Single<List<TaxiResponse>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@ 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<TaxiResponse> {
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<List<TaxiResponse>> {
return api.fetchTaxisByLocation(p1Lat = p1Lat.toString(), p1Lon = p1Lon.toString(), p2Lat = p2Lat.toString(), p2Lon = p2Lon.toString()).map {
return@map it.poiList ?: emptyList()
}
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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<TaxiResponse>, List<Taxi>> {
class TaxiMapper: Function<List<TaxiResponse>, List<Taxi>> {

override fun mapResponseToAppModel(toParse: List<TaxiResponse>): List<Taxi> {

override fun apply(toParse: List<TaxiResponse>): List<Taxi> {
if (toParse.isNullOrEmpty()) {
return emptyList()
}
val list = ArrayList<Taxi>()
toParse.forEach {
list.add(it.toTaxi())
}
return list
}

}
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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<TaxiApiResponse>
@Query("p2Lon") p2Lon: String): Single<TaxiApiResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ 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
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
Expand Down Expand Up @@ -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))
})

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,25 @@ 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<String>()
val taxiMapLiveData = MutableLiveData<HashMap<LatLng, RecyclerViewType>>()
val taxiMapLiveData = MutableLiveData<HashMap<LatLng, Taxi>>()

private val mapClickedLivedData = MutableLiveData<LatLng>()
private val itemCLickedLiveData = MutableLiveData<Taxi>()
private val requestListFocusLiveData: LiveData<RecyclerViewType>
private val itemCLickedLiveData = MutableLiveData<Taxi>()
private val requestListFocusLiveData: LiveData<Taxi>
private val requestMapFocusLiveData: LiveData<LatLng>

private var isLoading = false
private val mapItems: HashMap<LatLng, RecyclerViewType> = HashMap()
private var mapItems: HashMap<LatLng, Taxi> = HashMap()

init {
requestListFocusLiveData = Transformations.map(mapClickedLivedData, Function {
Expand All @@ -40,73 +36,52 @@ class TaxiViewModel(private val iRepository: IRepository,
})
}

fun fetchTaxisByLocationPage(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double) {
GlobalScope.launch(ioDispatcher) {
syncRepository(p1Lat,
p1Lon,
p2Lat,
p2Lon)
}
}

@Synchronized
@WorkerThread
private suspend fun syncRepository(p1Lat: Double, p1Lon: Double, p2Lat: Double, p2Lon: Double) {
if (!isLoading) {
isLoading = true
private var disposable: Disposable? = null

val response = iRepository.requestTaxisByLocation(p1Lat,
p1Lon,
p2Lat,
p2Lon)
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)
response.info?.let {
mapItems = response.info
publishUIResults(taxiMapLiveData, mapItems)
}
}
response.status == Status.LOADING -> isLoading = true

else -> {
handleErrorCase(response)
handleErrorCase(response.message)
}
}
}
}

@WorkerThread
private suspend fun handleSuccessCase(response: Resource<List<Taxi>>) {
isLoading = false
response.info?.let {
addItemsAndNotify(it)
}
}, {
handleErrorCase(it.message ?: "Something when wrong, please try later")
})
}

@WorkerThread
private suspend fun handleErrorCase(response: Resource<List<Taxi>>) {
isLoading = false
publishUIResults(errorLiveData, response.message)
override fun onCleared() {
super.onCleared()
disposable?.dispose()
}

@WorkerThread
private suspend fun addItemsAndNotify(itemsToAdd: List<RecyclerViewType>) {
itemsToAdd.forEach {
if (it is Taxi) {
mapItems[it.latLong] = it
}
}
publishUIResults(taxiMapLiveData, mapItems)
private fun handleErrorCase(message: String) {
publishUIResults(errorLiveData, message)
}

@UiThread
private suspend fun <T> publishUIResults(liveData: MutableLiveData<T>, data: T) {
withContext(mainDispatcher) {
liveData.value = data
}
private fun <T> publishUIResults(liveData: MutableLiveData<T>, data: T) {
liveData.value = data
}

@UiThread
fun setClickedMarker(latLng: LatLng) {
mapClickedLivedData.value = latLng
}

@UiThread
fun setClickedItem(taxi: Taxi) {
itemCLickedLiveData.value = taxi
Expand Down
Loading