From 62f8b50d256cc4ef78bc0f1fc7d4f72c9597b62a Mon Sep 17 00:00:00 2001 From: Serchinastico <54cymru@gmail.com> Date: Tue, 17 Sep 2019 12:13:41 +0200 Subject: [PATCH 1/8] First implementation of a flow to get all photos --- shared/build.gradle | 4 +- .../com/karumi/gallery/app/androidAsync.kt | 1 + .../com/karumi/gallery/app/GalleryInjector.kt | 6 ++- .../karumi/gallery/app/PhotoListPresenter.kt | 53 +++++++++++++------ .../com/karumi/gallery/domain/PhotosFlow.kt | 32 +++++++++++ .../com/karumi/gallery/usecase/GetPhotos.kt | 7 +-- 6 files changed, 81 insertions(+), 22 deletions(-) create mode 100644 shared/src/commonMain/kotlin/com/karumi/gallery/domain/PhotosFlow.kt diff --git a/shared/build.gradle b/shared/build.gradle index c1c1ff8..ba0c60d 100755 --- a/shared/build.gradle +++ b/shared/build.gradle @@ -31,6 +31,9 @@ kotlin { all { languageSettings { useExperimentalAnnotation('kotlin.Experimental') + useExperimentalAnnotation('kotlin.time.ExperimentalTime') + useExperimentalAnnotation('kotlinx.coroutines.FlowPreview') + useExperimentalAnnotation('kotlinx.coroutines.ExperimentalCoroutinesApi') } } commonMain { @@ -57,7 +60,6 @@ kotlin { androidMain { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version" diff --git a/shared/src/androidMain/kotlin/com/karumi/gallery/app/androidAsync.kt b/shared/src/androidMain/kotlin/com/karumi/gallery/app/androidAsync.kt index 936f70c..f865d47 100644 --- a/shared/src/androidMain/kotlin/com/karumi/gallery/app/androidAsync.kt +++ b/shared/src/androidMain/kotlin/com/karumi/gallery/app/androidAsync.kt @@ -3,6 +3,7 @@ package com.karumi.gallery.app import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlin.coroutines.CoroutineContext diff --git a/shared/src/commonMain/kotlin/com/karumi/gallery/app/GalleryInjector.kt b/shared/src/commonMain/kotlin/com/karumi/gallery/app/GalleryInjector.kt index ff8173f..0200d9f 100644 --- a/shared/src/commonMain/kotlin/com/karumi/gallery/app/GalleryInjector.kt +++ b/shared/src/commonMain/kotlin/com/karumi/gallery/app/GalleryInjector.kt @@ -2,6 +2,7 @@ package com.karumi.gallery.app import com.karumi.gallery.data.PhotosApiClient import com.karumi.gallery.data.getEngine +import com.karumi.gallery.domain.PhotosFlow import com.karumi.gallery.generated.KotlinConfig import com.karumi.gallery.usecase.GetPhotos import kotlin.native.concurrent.ThreadLocal @@ -24,5 +25,8 @@ open class InjectionModule { PhotosApiClient(getEngine(), KotlinConfig.UNPLASH_KEY) open fun getPhotos(): GetPhotos = - GetPhotos(getPhotosApiClient()) + GetPhotos(getPhotosFlow()) + + open fun getPhotosFlow(): PhotosFlow = + PhotosFlow(getPhotosApiClient()) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/karumi/gallery/app/PhotoListPresenter.kt b/shared/src/commonMain/kotlin/com/karumi/gallery/app/PhotoListPresenter.kt index 66c0d48..07ade7f 100644 --- a/shared/src/commonMain/kotlin/com/karumi/gallery/app/PhotoListPresenter.kt +++ b/shared/src/commonMain/kotlin/com/karumi/gallery/app/PhotoListPresenter.kt @@ -4,39 +4,46 @@ import com.karumi.gallery.logError import com.karumi.gallery.logInfo import com.karumi.gallery.model.PhotoShot import com.karumi.gallery.usecase.GetPhotos +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext class PhotoListPresenter( private val view: View, private val getAllPhotos: GetPhotos ) { - companion object { private const val TAG = "PhotoListPresenter" } - private var getPhotosJob: Job? = null + private lateinit var scope: PresenterCoroutineScope fun onCreate() { - view.showLoader() - getPhotosJob = launchInMain { - try { - logInfo(TAG, "Start getting photos") - - val allShots = getAllPhotos() - view += allShots - logInfo(TAG, "${allShots.size} photos received") - } catch (ex: Exception) { - logError(TAG, "Load photos error: ${ex.message}") - view.onLoadError() - } finally { - view.hideLoader() - } + scope = PresenterCoroutineScope(Dispatchers.Main) + + scope.launch { + view.showLoader() + getAllPhotos() + .flowOn(Dispatchers.Default) + .catch { + logError(TAG, "Load photos error: ${it.message}") + view.hideLoader() + view.onLoadError() + }.collect { + logInfo(TAG, "${it.size} photos received") + view.hideLoader() + view += it + } } } fun detachView() { - getPhotosJob?.cancel() + scope.viewDetached() } interface View { @@ -45,4 +52,16 @@ class PhotoListPresenter( fun hideLoader() fun onLoadError() } +} + +private class PresenterCoroutineScope( + context: CoroutineContext +) : CoroutineScope { + + private var onViewDetachJob = Job() + override val coroutineContext: CoroutineContext = context + onViewDetachJob + + fun viewDetached() { + onViewDetachJob.cancel() + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/karumi/gallery/domain/PhotosFlow.kt b/shared/src/commonMain/kotlin/com/karumi/gallery/domain/PhotosFlow.kt new file mode 100644 index 0000000..d3945c7 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/karumi/gallery/domain/PhotosFlow.kt @@ -0,0 +1,32 @@ +package com.karumi.gallery.domain + +import com.karumi.gallery.data.PhotosApiClient +import com.karumi.gallery.model.Photos +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlin.time.Duration +import kotlin.time.seconds + +class PhotosFlow(private val photosApiClient: PhotosApiClient) { + + private companion object { + val updateInterval = 5.seconds + } + + suspend fun get(): Flow = + timer(repeatEvery = updateInterval) + .map { photosApiClient.getPhotos() } +} + +private fun timer( + delay: Duration = Duration.ZERO, + repeatEvery: Duration +) = flow { + delay(delay.toLongMilliseconds()) + while (true) { + emit(Unit) + delay(repeatEvery.toLongMilliseconds()) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/karumi/gallery/usecase/GetPhotos.kt b/shared/src/commonMain/kotlin/com/karumi/gallery/usecase/GetPhotos.kt index cf8c5a5..fd6481b 100644 --- a/shared/src/commonMain/kotlin/com/karumi/gallery/usecase/GetPhotos.kt +++ b/shared/src/commonMain/kotlin/com/karumi/gallery/usecase/GetPhotos.kt @@ -1,8 +1,9 @@ package com.karumi.gallery.usecase -import com.karumi.gallery.data.PhotosApiClient +import com.karumi.gallery.domain.PhotosFlow import com.karumi.gallery.model.Photos +import kotlinx.coroutines.flow.Flow -class GetPhotos(private val photosApiClient: PhotosApiClient) { - suspend operator fun invoke(): Photos = photosApiClient.getPhotos() +class GetPhotos(private val photosFlow: PhotosFlow) { + suspend operator fun invoke(): Flow = photosFlow.get() } \ No newline at end of file From 303889b155b1ea9f1c114ed9666d9f81593f32ac Mon Sep 17 00:00:00 2001 From: Serchinastico <54cymru@gmail.com> Date: Tue, 17 Sep 2019 13:10:03 +0200 Subject: [PATCH 2/8] Big refactor to make it work on iOS --- androidApp/build.gradle.kts | 15 +++-- .../PhotoGallery/PhotoCollectionViewCell.xib | 10 ++-- shared/build.gradle | 18 +++--- .../com/karumi/gallery/app/androidAsync.kt | 10 ++-- .../karumi/gallery/app/PhotoListPresenter.kt | 57 +++++++------------ .../kotlin/com/karumi/gallery/app/async.kt | 5 +- .../kotlin/com/karumi/gallery/app/iosAsync.kt | 10 +++- 7 files changed, 61 insertions(+), 64 deletions(-) diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 0ecc81e..3844930 100755 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -27,7 +27,7 @@ android { } lintOptions { - setAbortOnError(false) + isAbortOnError = false } compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 @@ -46,15 +46,18 @@ dependencies { implementation(kotlin("stdlib-jdk7", KotlinCompilerVersion.VERSION)) implementation(project(":shared")) - implementation("androidx.constraintlayout:constraintlayout:2.0.0-beta1") - implementation("androidx.appcompat:appcompat:1.0.2") + implementation("androidx.constraintlayout:constraintlayout:2.0.0-beta2") + implementation("androidx.appcompat:appcompat:1.1.0") implementation("androidx.recyclerview:recyclerview:1.0.0") - implementation("androidx.lifecycle:lifecycle-runtime:2.0.0") - implementation("androidx.lifecycle:lifecycle-extensions:2.0.0") + implementation("androidx.lifecycle:lifecycle-runtime:2.1.0") + implementation("androidx.lifecycle:lifecycle-extensions:2.1.0") implementation("com.github.pedrovgs:renderers:3.4.0") - implementation("com.squareup.okhttp3:okhttp:3.11.0") + implementation("com.squareup.okhttp3:okhttp:3.12.1") implementation("com.squareup.picasso:picasso:2.71828") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1") + testImplementation("junit:junit:4.12") androidTestImplementation("org.mockito:mockito-android:2.28.2") androidTestImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0") diff --git a/iosApp/PhotoGallery/PhotoCollectionViewCell.xib b/iosApp/PhotoGallery/PhotoCollectionViewCell.xib index 169a230..22a9248 100644 --- a/iosApp/PhotoGallery/PhotoCollectionViewCell.xib +++ b/iosApp/PhotoGallery/PhotoCollectionViewCell.xib @@ -1,28 +1,28 @@ - + - + - + - + - +