From c347f47b54fc616873c54f7e1d317758132f83be Mon Sep 17 00:00:00 2001 From: skydoves Date: Tue, 13 Jul 2021 21:27:31 +0900 Subject: [PATCH 1/8] Bump dependencies version --- dependencies.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index 765af65..0aa61c3 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -6,14 +6,14 @@ ext.versions = [ // gradle plugins gradleBuildTool : '4.1.3', - spotlessGradle : '5.12.3', + spotlessGradle : '5.14.0', versionPlugin : '0.23.0', // kotlin kotlin : '1.4.32', // support library - materialVersion : '1.3.0', + materialVersion : '1.4.0', constraintVersion : '2.0.4', // architecture components @@ -30,7 +30,7 @@ ext.versions = [ // network retrofitVersion : '2.9.0', okhttpVersion : '4.9.0', - sandwichVersion : '1.0.9', + sandwichVersion : '1.2.0', // coroutines coroutinesVersion : '1.4.3', From 837a3d1ff2b61200bfd32f43e16fb725ba4c5abb Mon Sep 17 00:00:00 2001 From: skydoves Date: Tue, 13 Jul 2021 21:35:38 +0900 Subject: [PATCH 2/8] Remove setting lifecycleOwner --- .../java/com/skydoves/disneymotions/view/ui/main/HomeFragment.kt | 1 - .../com/skydoves/disneymotions/view/ui/main/LibraryFragment.kt | 1 - .../java/com/skydoves/disneymotions/view/ui/main/MainActivity.kt | 1 - .../com/skydoves/disneymotions/view/ui/main/RadioFragment.kt | 1 - 4 files changed, 4 deletions(-) diff --git a/app/src/main/java/com/skydoves/disneymotions/view/ui/main/HomeFragment.kt b/app/src/main/java/com/skydoves/disneymotions/view/ui/main/HomeFragment.kt index be99f04..188d753 100644 --- a/app/src/main/java/com/skydoves/disneymotions/view/ui/main/HomeFragment.kt +++ b/app/src/main/java/com/skydoves/disneymotions/view/ui/main/HomeFragment.kt @@ -42,7 +42,6 @@ class HomeFragment : BindingFragment(R.layout.fragment_home super.onCreateView(inflater, container, savedInstanceState) return binding { viewModel = getSharedViewModel() - lifecycleOwner = viewLifecycleOwner adapter = PosterAdapter() }.root } diff --git a/app/src/main/java/com/skydoves/disneymotions/view/ui/main/LibraryFragment.kt b/app/src/main/java/com/skydoves/disneymotions/view/ui/main/LibraryFragment.kt index 1d48bf9..2bea888 100644 --- a/app/src/main/java/com/skydoves/disneymotions/view/ui/main/LibraryFragment.kt +++ b/app/src/main/java/com/skydoves/disneymotions/view/ui/main/LibraryFragment.kt @@ -36,7 +36,6 @@ class LibraryFragment : BindingFragment(R.layout.fragmen super.onCreateView(inflater, container, savedInstanceState) return binding { viewModel = getSharedViewModel() - lifecycleOwner = viewLifecycleOwner adapter = PosterLineAdapter() }.root } diff --git a/app/src/main/java/com/skydoves/disneymotions/view/ui/main/MainActivity.kt b/app/src/main/java/com/skydoves/disneymotions/view/ui/main/MainActivity.kt index 49f2cdc..c52a13b 100644 --- a/app/src/main/java/com/skydoves/disneymotions/view/ui/main/MainActivity.kt +++ b/app/src/main/java/com/skydoves/disneymotions/view/ui/main/MainActivity.kt @@ -30,7 +30,6 @@ class MainActivity : BindingActivity(R.layout.activity_main super.onCreate(savedInstanceState) binding { pagerAdapter = MainPagerAdapter(this@MainActivity) - lifecycleOwner = this@MainActivity vm = getViewModel() } } diff --git a/app/src/main/java/com/skydoves/disneymotions/view/ui/main/RadioFragment.kt b/app/src/main/java/com/skydoves/disneymotions/view/ui/main/RadioFragment.kt index 0341379..a4425d5 100644 --- a/app/src/main/java/com/skydoves/disneymotions/view/ui/main/RadioFragment.kt +++ b/app/src/main/java/com/skydoves/disneymotions/view/ui/main/RadioFragment.kt @@ -36,7 +36,6 @@ class RadioFragment : BindingFragment(R.layout.fragment_ra super.onCreateView(inflater, container, savedInstanceState) return binding { viewModel = getSharedViewModel() - lifecycleOwner = viewLifecycleOwner adapter = PosterCircleAdapter() }.root } From dc78d6c0783719fd9bf91521433a71b5a9595bf2 Mon Sep 17 00:00:00 2001 From: skydoves Date: Tue, 13 Jul 2021 21:36:38 +0900 Subject: [PATCH 3/8] Refactor viewModels and repositories, migrate to a new sandwich version --- app/build.gradle | 5 ++- .../base/LiveCoroutinesViewModel.kt | 32 ------------------- .../disneymotions/di/NetworkModule.kt | 2 +- .../disneymotions/di/ViewModelModule.kt | 2 +- .../repository/MainRepository.kt | 12 +++---- .../view/ui/details/PosterDetailActivity.kt | 8 ++--- .../view/ui/details/PosterDetailViewModel.kt | 29 ++++++++--------- .../view/ui/main/MainViewModel.kt | 27 ++++++++-------- .../res/layout/activity_poster_detail.xml | 9 ++---- app/src/main/res/layout/fragment_home.xml | 2 +- app/src/main/res/layout/fragment_library.xml | 2 +- app/src/main/res/layout/fragment_radio.xml | 2 +- 12 files changed, 46 insertions(+), 86 deletions(-) delete mode 100644 app/src/main/java/com/skydoves/disneymotions/base/LiveCoroutinesViewModel.kt diff --git a/app/build.gradle b/app/build.gradle index 7e5275a..dfa8e5d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -39,6 +39,10 @@ android { returnDefaultValues = true } } + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlin.time.ExperimentalTime"] + kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"] + } } dependencies { @@ -48,7 +52,6 @@ dependencies { // architecture components implementation "androidx.lifecycle:lifecycle-extensions:$versions.lifecycleVersion" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:$versions.lifecycleVersion" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$versions.lifecycleVersion" implementation "androidx.room:room-runtime:$versions.roomVersion" implementation "androidx.room:room-ktx:$versions.roomVersion" diff --git a/app/src/main/java/com/skydoves/disneymotions/base/LiveCoroutinesViewModel.kt b/app/src/main/java/com/skydoves/disneymotions/base/LiveCoroutinesViewModel.kt deleted file mode 100644 index 6b66474..0000000 --- a/app/src/main/java/com/skydoves/disneymotions/base/LiveCoroutinesViewModel.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Designed and developed by 2020 skydoves (Jaewoong Eum) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.skydoves.disneymotions.base - -import androidx.lifecycle.LiveData -import androidx.lifecycle.liveData -import androidx.lifecycle.viewModelScope -import com.skydoves.bindables.BindingViewModel -import kotlinx.coroutines.Dispatchers - -abstract class LiveCoroutinesViewModel : BindingViewModel() { - - inline fun launchOnViewModelScope(crossinline block: suspend () -> LiveData): LiveData { - return liveData(viewModelScope.coroutineContext + Dispatchers.IO) { - emitSource(block()) - } - } -} diff --git a/app/src/main/java/com/skydoves/disneymotions/di/NetworkModule.kt b/app/src/main/java/com/skydoves/disneymotions/di/NetworkModule.kt index 7b03ee0..c1d49b3 100644 --- a/app/src/main/java/com/skydoves/disneymotions/di/NetworkModule.kt +++ b/app/src/main/java/com/skydoves/disneymotions/di/NetworkModule.kt @@ -39,7 +39,7 @@ val networkModule = module { "https://gist.githubusercontent.com/skydoves/aa3bbbf495b0fa91db8a9e89f34e4873/raw/a1a13d37027e8920412da5f00f6a89c5a3dbfb9a/" ) .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(CoroutinesResponseCallAdapterFactory()) + .addCallAdapterFactory(CoroutinesResponseCallAdapterFactory.create()) .build() } diff --git a/app/src/main/java/com/skydoves/disneymotions/di/ViewModelModule.kt b/app/src/main/java/com/skydoves/disneymotions/di/ViewModelModule.kt index d92d1e4..df41a67 100644 --- a/app/src/main/java/com/skydoves/disneymotions/di/ViewModelModule.kt +++ b/app/src/main/java/com/skydoves/disneymotions/di/ViewModelModule.kt @@ -25,5 +25,5 @@ val viewModelModule = module { viewModel { MainViewModel(get()) } - viewModel { PosterDetailViewModel(get()) } + viewModel { (posterId: Long) -> PosterDetailViewModel(posterId, get()) } } diff --git a/app/src/main/java/com/skydoves/disneymotions/repository/MainRepository.kt b/app/src/main/java/com/skydoves/disneymotions/repository/MainRepository.kt index 6733768..7518824 100644 --- a/app/src/main/java/com/skydoves/disneymotions/repository/MainRepository.kt +++ b/app/src/main/java/com/skydoves/disneymotions/repository/MainRepository.kt @@ -27,10 +27,10 @@ import com.skydoves.sandwich.message import com.skydoves.sandwich.onError import com.skydoves.sandwich.onException import com.skydoves.sandwich.suspendOnSuccess -import com.skydoves.whatif.whatIfNotNull import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onCompletion import timber.log.Timber class MainRepository constructor( @@ -53,11 +53,8 @@ class MainRepository constructor( disneyService.fetchDisneyPosterList() // handles the success case when the API request gets a successful response. .suspendOnSuccess { - data.whatIfNotNull { - posterDao.insertPosterList(it) - emit(it) - onSuccess() - } + posterDao.insertPosterList(data) + emit(data) } /** * handles error cases when the API request gets an error response. @@ -74,7 +71,6 @@ class MainRepository constructor( } } else { emit(posters) - onSuccess() } - }.flowOn(Dispatchers.IO) + }.onCompletion { onSuccess() }.flowOn(Dispatchers.IO) } diff --git a/app/src/main/java/com/skydoves/disneymotions/view/ui/details/PosterDetailActivity.kt b/app/src/main/java/com/skydoves/disneymotions/view/ui/details/PosterDetailActivity.kt index 7e0e9f2..f0d7bd6 100644 --- a/app/src/main/java/com/skydoves/disneymotions/view/ui/details/PosterDetailActivity.kt +++ b/app/src/main/java/com/skydoves/disneymotions/view/ui/details/PosterDetailActivity.kt @@ -31,22 +31,22 @@ import com.skydoves.disneymotions.databinding.ActivityPosterDetailBinding import com.skydoves.disneymotions.extensions.applyMaterialTransform import com.skydoves.disneymotions.model.Poster import com.skydoves.whatif.whatIfNotNullAs -import org.koin.android.viewmodel.ext.android.getViewModel +import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf class PosterDetailActivity : BindingActivity(R.layout.activity_poster_detail) { private val posterId: Long by bundle(EXTRA_POSTER_ID, -1) private val posterName: String by bundleNonNull(EXTRA_POSTER_NAME) + private val viewModel: PosterDetailViewModel by viewModel { parametersOf(posterId) } override fun onCreate(savedInstanceState: Bundle?) { applyMaterialTransform(posterName) super.onCreate(savedInstanceState) binding { - vm = getViewModel().getPoster(posterId) - lifecycleOwner = this@PosterDetailActivity activity = this@PosterDetailActivity - container = detailContainer + vm = viewModel fab = fabMore } } diff --git a/app/src/main/java/com/skydoves/disneymotions/view/ui/details/PosterDetailViewModel.kt b/app/src/main/java/com/skydoves/disneymotions/view/ui/details/PosterDetailViewModel.kt index 768bb53..a073b41 100644 --- a/app/src/main/java/com/skydoves/disneymotions/view/ui/details/PosterDetailViewModel.kt +++ b/app/src/main/java/com/skydoves/disneymotions/view/ui/details/PosterDetailViewModel.kt @@ -16,28 +16,25 @@ package com.skydoves.disneymotions.view.ui.details -import androidx.annotation.MainThread -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.asLiveData -import androidx.lifecycle.switchMap -import com.skydoves.disneymotions.base.LiveCoroutinesViewModel +import androidx.databinding.Bindable +import androidx.lifecycle.viewModelScope +import com.skydoves.bindables.BindingViewModel +import com.skydoves.bindables.asBindingProperty import com.skydoves.disneymotions.model.Poster import com.skydoves.disneymotions.repository.DetailRepository +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flatMapLatest class PosterDetailViewModel( + posterId: Long, private val repository: DetailRepository -) : LiveCoroutinesViewModel() { +) : BindingViewModel() { - private val posterIdLiveData: MutableLiveData = MutableLiveData() - val poster: LiveData = posterIdLiveData.switchMap { - launchOnViewModelScope { - repository.getPosterById(it).asLiveData() - } + private val posterIdStateFlow: MutableStateFlow = MutableStateFlow(posterId) + private val posterFlow = posterIdStateFlow.flatMapLatest { id -> + repository.getPosterById(id) } - @MainThread - fun getPoster(id: Long) = apply { - posterIdLiveData.value = id - } + @get:Bindable + val poster: Poster? by posterFlow.asBindingProperty(viewModelScope, null) } diff --git a/app/src/main/java/com/skydoves/disneymotions/view/ui/main/MainViewModel.kt b/app/src/main/java/com/skydoves/disneymotions/view/ui/main/MainViewModel.kt index 1c2613f..7b5143d 100644 --- a/app/src/main/java/com/skydoves/disneymotions/view/ui/main/MainViewModel.kt +++ b/app/src/main/java/com/skydoves/disneymotions/view/ui/main/MainViewModel.kt @@ -17,19 +17,17 @@ package com.skydoves.disneymotions.view.ui.main import androidx.databinding.Bindable -import androidx.lifecycle.LiveData -import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import com.skydoves.bindables.BindingViewModel +import com.skydoves.bindables.asBindingProperty import com.skydoves.bindables.bindingProperty -import com.skydoves.disneymotions.base.LiveCoroutinesViewModel import com.skydoves.disneymotions.model.Poster import com.skydoves.disneymotions.repository.MainRepository import timber.log.Timber class MainViewModel constructor( - private val mainRepository: MainRepository -) : LiveCoroutinesViewModel() { - - val posterListLiveData: LiveData> + mainRepository: MainRepository +) : BindingViewModel() { @get:Bindable var isLoading: Boolean by bindingProperty(true) @@ -39,14 +37,15 @@ class MainViewModel constructor( var errorToast: String? by bindingProperty(null) private set + private val posterListFlow = mainRepository.loadDisneyPosters( + onSuccess = { isLoading = false }, + onError = { errorToast = it } + ) + + @get:Bindable + val posterList: List by posterListFlow.asBindingProperty(viewModelScope, emptyList()) + init { Timber.d("injection MainViewModel") - - posterListLiveData = launchOnViewModelScope { - this.mainRepository.loadDisneyPosters( - onSuccess = { isLoading = false }, - onError = { errorToast = it } - ).asLiveData() - } } } diff --git a/app/src/main/res/layout/activity_poster_detail.xml b/app/src/main/res/layout/activity_poster_detail.xml index 1799c82..5dcf5c5 100644 --- a/app/src/main/res/layout/activity_poster_detail.xml +++ b/app/src/main/res/layout/activity_poster_detail.xml @@ -13,10 +13,6 @@ name="activity" type="androidx.appcompat.app.AppCompatActivity" /> - - @@ -71,7 +67,8 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/profile_detail_background" app:layout_goneMarginBottom="26dp" - app:tint="@color/white" /> + app:tint="@color/white" + tools:ignore="ContentDescription" /> Date: Tue, 13 Jul 2021 21:40:42 +0900 Subject: [PATCH 4/8] Update unit test codes --- app/build.gradle | 1 + .../test/java/com/skydoves/disneymotions/network/ApiAbstract.kt | 2 +- .../disneymotions/viewmodel/PosterDetailViewModelTest.kt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index dfa8e5d..e3cfec4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -52,6 +52,7 @@ dependencies { // architecture components implementation "androidx.lifecycle:lifecycle-extensions:$versions.lifecycleVersion" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:$versions.lifecycleVersion" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$versions.lifecycleVersion" implementation "androidx.room:room-runtime:$versions.roomVersion" implementation "androidx.room:room-ktx:$versions.roomVersion" diff --git a/app/src/test/java/com/skydoves/disneymotions/network/ApiAbstract.kt b/app/src/test/java/com/skydoves/disneymotions/network/ApiAbstract.kt index 6b7e530..c9850f5 100644 --- a/app/src/test/java/com/skydoves/disneymotions/network/ApiAbstract.kt +++ b/app/src/test/java/com/skydoves/disneymotions/network/ApiAbstract.kt @@ -74,7 +74,7 @@ abstract class ApiAbstract { return Retrofit.Builder() .baseUrl(mockWebServer.url("/")) .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(CoroutinesResponseCallAdapterFactory()) + .addCallAdapterFactory(CoroutinesResponseCallAdapterFactory.create()) .build() .create(clazz) } diff --git a/app/src/test/java/com/skydoves/disneymotions/viewmodel/PosterDetailViewModelTest.kt b/app/src/test/java/com/skydoves/disneymotions/viewmodel/PosterDetailViewModelTest.kt index 669f42e..7512956 100644 --- a/app/src/test/java/com/skydoves/disneymotions/viewmodel/PosterDetailViewModelTest.kt +++ b/app/src/test/java/com/skydoves/disneymotions/viewmodel/PosterDetailViewModelTest.kt @@ -53,7 +53,7 @@ class PosterDetailViewModelTest { @Before fun setup() { repository = DetailRepository(posterDao) - viewModel = PosterDetailViewModel(repository) + viewModel = PosterDetailViewModel(0L, repository) } @Test From 30926e7871e8b3453507727864d6362eb32d21e4 Mon Sep 17 00:00:00 2001 From: skydoves Date: Tue, 13 Jul 2021 21:43:30 +0900 Subject: [PATCH 5/8] Update README file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b0d04d..53adcc1 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,12 @@ Go to the [Releases](https://github.com/skydoves/DisneyMotions/releases) to down - Minimum SDK level 21 - 100% [Kotlin](https://kotlinlang.org/) based + [Coroutines](https://github.com/Kotlin/kotlinx.coroutines) + [Flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/) for asynchronous. - JetPack - - LiveData - notify domain layer data to views. - Lifecycle - dispose observing data when lifecycle state changes. - ViewModel - UI related data holder, lifecycle aware. - Room Persistence - construct database. - Architecture - MVVM Architecture (View - DataBinding - ViewModel - Model) + - [Bindables](https://github.com/skydoves/bindables) - Android DataBinding kit for notifying data changes to UI layers. - Repository pattern - [Koin](https://github.com/InsertKoinIO/koin) - dependency injection - Material Design & Animations From 4db2789e0019d86ef5b25a50e11b419895bb054d Mon Sep 17 00:00:00 2001 From: skydoves Date: Wed, 14 Jul 2021 00:00:44 +0900 Subject: [PATCH 6/8] Implement GlobalResponseOperator for processing a global operator --- .../network/GlobalResponseOperator.kt | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 app/src/main/java/com/skydoves/disneymotions/network/GlobalResponseOperator.kt diff --git a/app/src/main/java/com/skydoves/disneymotions/network/GlobalResponseOperator.kt b/app/src/main/java/com/skydoves/disneymotions/network/GlobalResponseOperator.kt new file mode 100644 index 0000000..2393d1d --- /dev/null +++ b/app/src/main/java/com/skydoves/disneymotions/network/GlobalResponseOperator.kt @@ -0,0 +1,67 @@ +/* + * Designed and developed by 2020 skydoves (Jaewoong Eum) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.skydoves.disneymotions.network + +import android.app.Application +import android.widget.Toast +import com.skydoves.disneymotions.mapper.ErrorResponseMapper +import com.skydoves.sandwich.ApiResponse +import com.skydoves.sandwich.StatusCode +import com.skydoves.sandwich.map +import com.skydoves.sandwich.message +import com.skydoves.sandwich.operators.ApiResponseSuspendOperator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import timber.log.Timber + +class GlobalResponseOperator constructor( + private val application: Application +) : ApiResponseSuspendOperator() { + + override suspend fun onError(apiResponse: ApiResponse.Failure.Error) = + withContext(Dispatchers.IO) { + apiResponse.run { + Timber.d(message()) + + withContext(Dispatchers.Main) { + when (statusCode) { + StatusCode.InternalServerError -> toast("InternalServerError") + StatusCode.BadGateway -> toast("BadGateway") + else -> toast("$statusCode(${statusCode.code}): ${message()}") + } + } + + map(ErrorResponseMapper) { + Timber.d(message()) + } + } + } + + override suspend fun onException(apiResponse: ApiResponse.Failure.Exception) = + withContext(Dispatchers.Main) { + apiResponse.run { + Timber.d(message()) + toast(message()) + } + } + + override suspend fun onSuccess(apiResponse: ApiResponse.Success) = Unit + + private fun toast(message: String) { + Toast.makeText(application, message, Toast.LENGTH_SHORT).show() + } +} From d726c6a307b98e44e3b00ee3b72a022a8cbec610 Mon Sep 17 00:00:00 2001 From: skydoves Date: Wed, 14 Jul 2021 00:01:24 +0900 Subject: [PATCH 7/8] Set sandwichOperator and remove toast binding adapters --- .../java/com/skydoves/disneymotions/DisneyApplication.kt | 5 +++++ .../disneymotions/binding/RecyclerViewBinding.kt | 9 --------- .../skydoves/disneymotions/repository/MainRepository.kt | 5 ++--- .../skydoves/disneymotions/view/ui/main/MainViewModel.kt | 7 +------ app/src/main/res/layout/fragment_home.xml | 1 - app/src/main/res/layout/fragment_library.xml | 1 - app/src/main/res/layout/fragment_radio.xml | 1 - .../disneymotions/repository/MainRepositoryTest.kt | 3 +-- .../disneymotions/viewmodel/MainViewModelTest.kt | 3 +-- 9 files changed, 10 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/skydoves/disneymotions/DisneyApplication.kt b/app/src/main/java/com/skydoves/disneymotions/DisneyApplication.kt index bfe3e7e..bfc7280 100644 --- a/app/src/main/java/com/skydoves/disneymotions/DisneyApplication.kt +++ b/app/src/main/java/com/skydoves/disneymotions/DisneyApplication.kt @@ -23,6 +23,8 @@ import com.skydoves.disneymotions.di.networkModule import com.skydoves.disneymotions.di.persistenceModule import com.skydoves.disneymotions.di.repositoryModule import com.skydoves.disneymotions.di.viewModelModule +import com.skydoves.disneymotions.network.GlobalResponseOperator +import com.skydoves.sandwich.SandwichInitializer import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin import timber.log.Timber @@ -40,6 +42,9 @@ class DisneyApplication : Application() { modules(viewModelModule) } + // initialize global sandwich operator + SandwichInitializer.sandwichOperator = GlobalResponseOperator(this) + if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) } diff --git a/app/src/main/java/com/skydoves/disneymotions/binding/RecyclerViewBinding.kt b/app/src/main/java/com/skydoves/disneymotions/binding/RecyclerViewBinding.kt index 59a3aa1..2d52ebb 100644 --- a/app/src/main/java/com/skydoves/disneymotions/binding/RecyclerViewBinding.kt +++ b/app/src/main/java/com/skydoves/disneymotions/binding/RecyclerViewBinding.kt @@ -16,7 +16,6 @@ package com.skydoves.disneymotions.binding -import android.widget.Toast import androidx.databinding.BindingAdapter import androidx.recyclerview.widget.RecyclerView import com.skydoves.baserecyclerviewadapter.BaseAdapter @@ -34,14 +33,6 @@ object RecyclerViewBinding { view.adapter = baseAdapter } - @JvmStatic - @BindingAdapter("toast") - fun bindToast(view: RecyclerView, text: String?) { - text.whatIfNotNullOrEmpty { - Toast.makeText(view.context, it, Toast.LENGTH_SHORT).show() - } - } - @JvmStatic @BindingAdapter("adapterPosterList") fun bindAdapterPosterList(view: RecyclerView, posters: List?) { diff --git a/app/src/main/java/com/skydoves/disneymotions/repository/MainRepository.kt b/app/src/main/java/com/skydoves/disneymotions/repository/MainRepository.kt index 7518824..5aef3e9 100644 --- a/app/src/main/java/com/skydoves/disneymotions/repository/MainRepository.kt +++ b/app/src/main/java/com/skydoves/disneymotions/repository/MainRepository.kt @@ -45,7 +45,6 @@ class MainRepository constructor( @WorkerThread fun loadDisneyPosters( onSuccess: () -> Unit, - onError: (String) -> Unit ) = flow { val posters: List = posterDao.getPosterList() if (posters.isEmpty()) { @@ -62,12 +61,12 @@ class MainRepository constructor( * maps the [ApiResponse.Failure.Error] to the [PosterErrorResponse] using the mapper. */ .onError(ErrorResponseMapper) { - onError("[Code: $code]: $message") + Timber.d("[Code: $code]: $message") } // handles exceptional cases when the API request gets an exception response. // e.g., network connection error. .onException { - onError(message()) + Timber.d(message()) } } else { emit(posters) diff --git a/app/src/main/java/com/skydoves/disneymotions/view/ui/main/MainViewModel.kt b/app/src/main/java/com/skydoves/disneymotions/view/ui/main/MainViewModel.kt index 7b5143d..c8d4c72 100644 --- a/app/src/main/java/com/skydoves/disneymotions/view/ui/main/MainViewModel.kt +++ b/app/src/main/java/com/skydoves/disneymotions/view/ui/main/MainViewModel.kt @@ -33,13 +33,8 @@ class MainViewModel constructor( var isLoading: Boolean by bindingProperty(true) private set - @get:Bindable - var errorToast: String? by bindingProperty(null) - private set - private val posterListFlow = mainRepository.loadDisneyPosters( - onSuccess = { isLoading = false }, - onError = { errorToast = it } + onSuccess = { isLoading = false } ) @get:Bindable diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 5fcaeb1..a94d9a3 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -29,7 +29,6 @@ app:adapterPosterList="@{viewModel.posterList}" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:spanCount="2" - app:toast="@{viewModel.errorToast}" tools:listitem="@layout/item_poster" /> diff --git a/app/src/main/res/layout/fragment_radio.xml b/app/src/main/res/layout/fragment_radio.xml index 57dc4d2..5efb4f1 100644 --- a/app/src/main/res/layout/fragment_radio.xml +++ b/app/src/main/res/layout/fragment_radio.xml @@ -33,7 +33,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:spanCount="2" - app:toast="@{viewModel.errorToast}" tools:listitem="@layout/item_poster_circle" /> diff --git a/app/src/test/java/com/skydoves/disneymotions/repository/MainRepositoryTest.kt b/app/src/test/java/com/skydoves/disneymotions/repository/MainRepositoryTest.kt index 5ac0714..6e6461a 100644 --- a/app/src/test/java/com/skydoves/disneymotions/repository/MainRepositoryTest.kt +++ b/app/src/test/java/com/skydoves/disneymotions/repository/MainRepositoryTest.kt @@ -67,8 +67,7 @@ class MainRepositoryTest { ) repository.loadDisneyPosters( - onSuccess = {}, - onError = {} + onSuccess = {} ).collect { assertThat(it[0].id, `is`(0L)) assertThat(it[0].name, `is`("Frozen II")) diff --git a/app/src/test/java/com/skydoves/disneymotions/viewmodel/MainViewModelTest.kt b/app/src/test/java/com/skydoves/disneymotions/viewmodel/MainViewModelTest.kt index 676b86e..de68c16 100644 --- a/app/src/test/java/com/skydoves/disneymotions/viewmodel/MainViewModelTest.kt +++ b/app/src/test/java/com/skydoves/disneymotions/viewmodel/MainViewModelTest.kt @@ -65,8 +65,7 @@ class MainViewModelTest { whenever(posterDao.getPosterList()).thenReturn(mockData) val fetchedData = mainRepository.loadDisneyPosters( - onSuccess = {}, - onError = {} + onSuccess = {} ).asLiveData() val observer: Observer> = mock() From 50eb2fb1b5b52d0cb5a7c2140040dccba92d2343 Mon Sep 17 00:00:00 2001 From: skydoves Date: Wed, 14 Jul 2021 00:04:13 +0900 Subject: [PATCH 8/8] Update IO thread to Main thread for debugging error messages --- .../disneymotions/network/GlobalResponseOperator.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/skydoves/disneymotions/network/GlobalResponseOperator.kt b/app/src/main/java/com/skydoves/disneymotions/network/GlobalResponseOperator.kt index 2393d1d..f31a187 100644 --- a/app/src/main/java/com/skydoves/disneymotions/network/GlobalResponseOperator.kt +++ b/app/src/main/java/com/skydoves/disneymotions/network/GlobalResponseOperator.kt @@ -33,16 +33,14 @@ class GlobalResponseOperator constructor( ) : ApiResponseSuspendOperator() { override suspend fun onError(apiResponse: ApiResponse.Failure.Error) = - withContext(Dispatchers.IO) { + withContext(Dispatchers.Main) { apiResponse.run { Timber.d(message()) - withContext(Dispatchers.Main) { - when (statusCode) { - StatusCode.InternalServerError -> toast("InternalServerError") - StatusCode.BadGateway -> toast("BadGateway") - else -> toast("$statusCode(${statusCode.code}): ${message()}") - } + when (statusCode) { + StatusCode.InternalServerError -> toast("InternalServerError") + StatusCode.BadGateway -> toast("BadGateway") + else -> toast("$statusCode(${statusCode.code}): ${message()}") } map(ErrorResponseMapper) {