From 05611574a0694af337f65f8917d838f1ee3acb41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=80=D0=B8=D1=81=D0=BE=D0=B2=D1=81=D0=BA?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=90=D0=BB=D0=B5=D0=BD=D0=B0?= Date: Sat, 7 Oct 2023 18:03:56 +0300 Subject: [PATCH 1/4] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B9=D1=82=D0=B8?= =?UTF-8?q?=20=D1=81=20=D0=BA=D0=BE=D0=BB=D0=BB=D0=B1=D0=B5=D0=BA=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=BD=D0=B0=20=D1=81=D0=B0=D1=81=D0=BF=D0=B5=D0=BD?= =?UTF-8?q?=D0=B4=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20=D0=B8=20?= =?UTF-8?q?=D0=BA=D0=BE=D1=80=D1=83=D1=82=D0=B8=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 6 ++- .../otus/homework/coroutines/CatsPresenter.kt | 37 +++++++++++----- .../otus/homework/coroutines/CatsService.kt | 2 +- .../java/otus/homework/coroutines/CatsView.kt | 9 +++- .../otus/homework/coroutines/CrashMonitor.kt | 3 ++ .../otus/homework/coroutines/DiContainer.kt | 5 +++ .../java/otus/homework/coroutines/Fact.kt | 42 +++++++++++-------- .../otus/homework/coroutines/MainActivity.kt | 4 +- build.gradle | 2 +- 9 files changed, 76 insertions(+), 34 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 679dbba4..df872a07 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,13 +4,13 @@ plugins { } android { - compileSdkVersion 30 + compileSdkVersion 32 buildToolsVersion "30.0.3" defaultConfig { applicationId "otus.homework.coroutines" minSdkVersion 23 - targetSdkVersion 30 + targetSdkVersion 32 versionCode 1 versionName "1.0" @@ -34,6 +34,8 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1' implementation 'androidx.core:core-ktx:1.3.2' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' diff --git a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt b/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt index e4b05120..4dd33749 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt @@ -1,28 +1,41 @@ package otus.homework.coroutines +import android.util.Log +import android.widget.Toast +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import retrofit2.Call import retrofit2.Callback import retrofit2.Response class CatsPresenter( - private val catsService: CatsService + private val catsService: CatsService, + private val presenterScope: CoroutineScope ) { private var _catsView: ICatsView? = null fun onInitComplete() { - catsService.getCatFact().enqueue(object : Callback { - - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful && response.body() != null) { - _catsView?.populate(response.body()!!) + presenterScope.launch { + try { +// throw java.net.SocketTimeoutException() + val fact = withContext(Dispatchers.IO) { + catsService.getCatFact() } - } - - override fun onFailure(call: Call, t: Throwable) { + Log.d("TAG", "Fact is $fact") + _catsView?.populate(fact) + } catch (sockExcept: java.net.SocketTimeoutException) { + _catsView?.showErrorToast("Не удалось получить ответ от сервера") + } catch (e: Exception) { + _catsView?.showErrorToast("${e.message}") CrashMonitor.trackWarning() } - }) + } } fun attachView(catsView: ICatsView) { @@ -32,4 +45,8 @@ class CatsPresenter( fun detachView() { _catsView = null } + + fun cancelCoroutine() { + presenterScope.coroutineContext.job.cancel() + } } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CatsService.kt b/app/src/main/java/otus/homework/coroutines/CatsService.kt index 479b2cfb..ca9e3f73 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsService.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsService.kt @@ -6,5 +6,5 @@ import retrofit2.http.GET interface CatsService { @GET("fact") - fun getCatFact() : Call + suspend fun getCatFact() : Fact } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CatsView.kt b/app/src/main/java/otus/homework/coroutines/CatsView.kt index 30ac2531..fc715fd0 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsView.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsView.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.AttributeSet import android.widget.Button import android.widget.TextView +import android.widget.Toast import androidx.constraintlayout.widget.ConstraintLayout class CatsView @JvmOverloads constructor( @@ -22,11 +23,17 @@ class CatsView @JvmOverloads constructor( } override fun populate(fact: Fact) { - findViewById(R.id.fact_textView).text = fact.text + findViewById(R.id.fact_textView).text = fact.fact + } + + override fun showErrorToast(errorMsg: String) { + Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show() } } interface ICatsView { fun populate(fact: Fact) + + fun showErrorToast(errorMsg: String) } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt b/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt index 32e6b018..b74fa4b6 100644 --- a/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt +++ b/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt @@ -1,10 +1,13 @@ package otus.homework.coroutines +import android.util.Log + object CrashMonitor { /** * Pretend this is Crashlytics/AppCenter */ fun trackWarning() { + Log.d("TAG", "Crash logged") } } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/DiContainer.kt b/app/src/main/java/otus/homework/coroutines/DiContainer.kt index 23ddc3b2..5d79fbd6 100644 --- a/app/src/main/java/otus/homework/coroutines/DiContainer.kt +++ b/app/src/main/java/otus/homework/coroutines/DiContainer.kt @@ -1,5 +1,8 @@ package otus.homework.coroutines +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory @@ -13,4 +16,6 @@ class DiContainer { } val service by lazy { retrofit.create(CatsService::class.java) } + + val presenterScope by lazy { CoroutineScope(Dispatchers.Main + CoroutineName("CatsCoroutine")) } } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/Fact.kt b/app/src/main/java/otus/homework/coroutines/Fact.kt index 15c6c7ae..c9d9e819 100644 --- a/app/src/main/java/otus/homework/coroutines/Fact.kt +++ b/app/src/main/java/otus/homework/coroutines/Fact.kt @@ -2,23 +2,29 @@ package otus.homework.coroutines import com.google.gson.annotations.SerializedName +//data class Fact( +// @field:SerializedName("createdAt") +// val createdAt: String, +// @field:SerializedName("deleted") +// val deleted: Boolean, +// @field:SerializedName("_id") +// val id: String, +// @field:SerializedName("text") +// val text: String, +// @field:SerializedName("source") +// val source: String, +// @field:SerializedName("used") +// val used: Boolean, +// @field:SerializedName("type") +// val type: String, +// @field:SerializedName("user") +// val user: String, +// @field:SerializedName("updatedAt") +// val updatedAt: String +//) data class Fact( - @field:SerializedName("createdAt") - val createdAt: String, - @field:SerializedName("deleted") - val deleted: Boolean, - @field:SerializedName("_id") - val id: String, - @field:SerializedName("text") - val text: String, - @field:SerializedName("source") - val source: String, - @field:SerializedName("used") - val used: Boolean, - @field:SerializedName("type") - val type: String, - @field:SerializedName("user") - val user: String, - @field:SerializedName("updatedAt") - val updatedAt: String + @field:SerializedName("fact") + val fact: String, + @field:SerializedName("length") + val length: Int ) \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/MainActivity.kt b/app/src/main/java/otus/homework/coroutines/MainActivity.kt index a9dafb3b..992390f5 100644 --- a/app/src/main/java/otus/homework/coroutines/MainActivity.kt +++ b/app/src/main/java/otus/homework/coroutines/MainActivity.kt @@ -2,6 +2,7 @@ package otus.homework.coroutines import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.util.Log class MainActivity : AppCompatActivity() { @@ -15,7 +16,7 @@ class MainActivity : AppCompatActivity() { val view = layoutInflater.inflate(R.layout.activity_main, null) as CatsView setContentView(view) - catsPresenter = CatsPresenter(diContainer.service) + catsPresenter = CatsPresenter(diContainer.service, diContainer.presenterScope) view.presenter = catsPresenter catsPresenter.attachView(view) catsPresenter.onInitComplete() @@ -25,6 +26,7 @@ class MainActivity : AppCompatActivity() { if (isFinishing) { catsPresenter.detachView() } + catsPresenter.cancelCoroutine() super.onStop() } } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8ca0a2e2..7e67df4e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.6.20" + ext.kotlin_version = "1.8.0" repositories { mavenCentral() google() From d223a5c714728fd3b25763da05b6bdb48ead0924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=80=D0=B8=D1=81=D0=BE=D0=B2=D1=81=D0=BA?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=90=D0=BB=D0=B5=D0=BD=D0=B0?= Date: Sun, 8 Oct 2023 15:28:46 +0300 Subject: [PATCH 2/4] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BA=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D1=83=20?= =?UTF-8?q?=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D0=B2=20=D0=B7=D0=B0=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=81=20=D1=80=D0=B0=D0=BD=D0=B4=D0=BE=D0=BC=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D0=BA=D0=B0=D1=80=D1=82=D0=B8=D0=BD=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 2 +- .../main/java/otus/homework/coroutines/Cat.kt | 6 ++ .../otus/homework/coroutines/CatsPresenter.kt | 52 ----------------- .../otus/homework/coroutines/CatsService.kt | 10 ---- .../otus/homework/coroutines/DiContainer.kt | 22 +++++-- .../java/otus/homework/coroutines/Fact.kt | 20 ------- .../java/otus/homework/coroutines/Image.kt | 14 +++++ .../coroutines/data/CatFactsService.kt | 10 ++++ .../coroutines/data/CatImagesService.kt | 9 +++ .../coroutines/domain/CatRepository.kt | 10 ++++ .../coroutines/domain/CatsPresenter.kt | 57 +++++++++++++++++++ .../coroutines/{ => presentation}/CatsView.kt | 18 ++++-- .../{ => presentation}/MainActivity.kt | 12 +++- app/src/main/res/layout/activity_main.xml | 17 +++++- 14 files changed, 161 insertions(+), 98 deletions(-) create mode 100644 app/src/main/java/otus/homework/coroutines/Cat.kt delete mode 100644 app/src/main/java/otus/homework/coroutines/CatsPresenter.kt delete mode 100644 app/src/main/java/otus/homework/coroutines/CatsService.kt create mode 100644 app/src/main/java/otus/homework/coroutines/Image.kt create mode 100644 app/src/main/java/otus/homework/coroutines/data/CatFactsService.kt create mode 100644 app/src/main/java/otus/homework/coroutines/data/CatImagesService.kt create mode 100644 app/src/main/java/otus/homework/coroutines/domain/CatRepository.kt create mode 100644 app/src/main/java/otus/homework/coroutines/domain/CatsPresenter.kt rename app/src/main/java/otus/homework/coroutines/{ => presentation}/CatsView.kt (58%) rename app/src/main/java/otus/homework/coroutines/{ => presentation}/MainActivity.kt (67%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fe34985b..fa531f7d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Coroutines"> - diff --git a/app/src/main/java/otus/homework/coroutines/Cat.kt b/app/src/main/java/otus/homework/coroutines/Cat.kt new file mode 100644 index 00000000..82063859 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/Cat.kt @@ -0,0 +1,6 @@ +package otus.homework.coroutines + +data class Cat( + val fact: String, + val imageUrl: String +) \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt b/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt deleted file mode 100644 index 4dd33749..00000000 --- a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt +++ /dev/null @@ -1,52 +0,0 @@ -package otus.homework.coroutines - -import android.util.Log -import android.widget.Toast -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.job -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response - -class CatsPresenter( - private val catsService: CatsService, - private val presenterScope: CoroutineScope -) { - - private var _catsView: ICatsView? = null - - fun onInitComplete() { - presenterScope.launch { - try { -// throw java.net.SocketTimeoutException() - val fact = withContext(Dispatchers.IO) { - catsService.getCatFact() - } - Log.d("TAG", "Fact is $fact") - _catsView?.populate(fact) - } catch (sockExcept: java.net.SocketTimeoutException) { - _catsView?.showErrorToast("Не удалось получить ответ от сервера") - } catch (e: Exception) { - _catsView?.showErrorToast("${e.message}") - CrashMonitor.trackWarning() - } - } - } - - fun attachView(catsView: ICatsView) { - _catsView = catsView - } - - fun detachView() { - _catsView = null - } - - fun cancelCoroutine() { - presenterScope.coroutineContext.job.cancel() - } -} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CatsService.kt b/app/src/main/java/otus/homework/coroutines/CatsService.kt deleted file mode 100644 index ca9e3f73..00000000 --- a/app/src/main/java/otus/homework/coroutines/CatsService.kt +++ /dev/null @@ -1,10 +0,0 @@ -package otus.homework.coroutines - -import retrofit2.Call -import retrofit2.http.GET - -interface CatsService { - - @GET("fact") - suspend fun getCatFact() : Fact -} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/DiContainer.kt b/app/src/main/java/otus/homework/coroutines/DiContainer.kt index 5d79fbd6..9b5efc8a 100644 --- a/app/src/main/java/otus/homework/coroutines/DiContainer.kt +++ b/app/src/main/java/otus/homework/coroutines/DiContainer.kt @@ -3,19 +3,33 @@ package otus.homework.coroutines import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import otus.homework.coroutines.data.CatFactsService +import otus.homework.coroutines.data.CatImagesService import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory class DiContainer { - private val retrofit by lazy { + val catFactsService by lazy { Retrofit.Builder() - .baseUrl("https://catfact.ninja/") + .baseUrl(FACT_URL) .addConverterFactory(GsonConverterFactory.create()) .build() + .create(CatFactsService::class.java) } - val service by lazy { retrofit.create(CatsService::class.java) } + val catImagesService by lazy { + Retrofit.Builder() + .baseUrl(IMAGE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(CatImagesService::class.java) + } val presenterScope by lazy { CoroutineScope(Dispatchers.Main + CoroutineName("CatsCoroutine")) } -} \ No newline at end of file + + companion object { + private const val FACT_URL = "https://catfact.ninja/" + private const val IMAGE_URL = "https://api.thecatapi.com/v1/images/" + } +} diff --git a/app/src/main/java/otus/homework/coroutines/Fact.kt b/app/src/main/java/otus/homework/coroutines/Fact.kt index c9d9e819..643a5a33 100644 --- a/app/src/main/java/otus/homework/coroutines/Fact.kt +++ b/app/src/main/java/otus/homework/coroutines/Fact.kt @@ -2,26 +2,6 @@ package otus.homework.coroutines import com.google.gson.annotations.SerializedName -//data class Fact( -// @field:SerializedName("createdAt") -// val createdAt: String, -// @field:SerializedName("deleted") -// val deleted: Boolean, -// @field:SerializedName("_id") -// val id: String, -// @field:SerializedName("text") -// val text: String, -// @field:SerializedName("source") -// val source: String, -// @field:SerializedName("used") -// val used: Boolean, -// @field:SerializedName("type") -// val type: String, -// @field:SerializedName("user") -// val user: String, -// @field:SerializedName("updatedAt") -// val updatedAt: String -//) data class Fact( @field:SerializedName("fact") val fact: String, diff --git a/app/src/main/java/otus/homework/coroutines/Image.kt b/app/src/main/java/otus/homework/coroutines/Image.kt new file mode 100644 index 00000000..dcc4872a --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/Image.kt @@ -0,0 +1,14 @@ +package otus.homework.coroutines + +import com.google.gson.annotations.SerializedName + +data class Image( + @field:SerializedName("id") + val id: String, + @field:SerializedName("url") + val url: String, + @field:SerializedName("width") + val width: Int, + @field:SerializedName("height") + val height: Int +) \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/data/CatFactsService.kt b/app/src/main/java/otus/homework/coroutines/data/CatFactsService.kt new file mode 100644 index 00000000..a6ed0913 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/data/CatFactsService.kt @@ -0,0 +1,10 @@ +package otus.homework.coroutines.data + +import otus.homework.coroutines.Fact +import retrofit2.http.GET + +interface CatFactsService { + + @GET("fact") + suspend fun getCatFact() : Fact +} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/data/CatImagesService.kt b/app/src/main/java/otus/homework/coroutines/data/CatImagesService.kt new file mode 100644 index 00000000..08324e43 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/data/CatImagesService.kt @@ -0,0 +1,9 @@ +package otus.homework.coroutines.data + +import otus.homework.coroutines.Image +import retrofit2.http.GET + +interface CatImagesService { + @GET("search") + suspend fun getCatImage(): Array +} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/domain/CatRepository.kt b/app/src/main/java/otus/homework/coroutines/domain/CatRepository.kt new file mode 100644 index 00000000..9446b831 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/domain/CatRepository.kt @@ -0,0 +1,10 @@ +package otus.homework.coroutines.domain + +import otus.homework.coroutines.data.CatFactsService +import otus.homework.coroutines.data.CatImagesService + +class CatRepository( + private val catFactsService: CatFactsService, + private val catImagesService: CatImagesService +) { +} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/domain/CatsPresenter.kt b/app/src/main/java/otus/homework/coroutines/domain/CatsPresenter.kt new file mode 100644 index 00000000..e69537e1 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/domain/CatsPresenter.kt @@ -0,0 +1,57 @@ +package otus.homework.coroutines.domain + +import android.util.Log +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import otus.homework.coroutines.Cat +import otus.homework.coroutines.CrashMonitor +import otus.homework.coroutines.data.CatFactsService +import otus.homework.coroutines.data.CatImagesService +import otus.homework.coroutines.presentation.ICatsView + +class CatsPresenter( + private val CatFactsService: CatFactsService, + private val CatImagesService: CatImagesService, + private val presenterScope: CoroutineScope +) { + + private var _catsView: ICatsView? = null + + fun onInitComplete() { + presenterScope.launch { + try { + val factJob = async(Dispatchers.IO) { + CatFactsService.getCatFact() + } + val imageJob = async(Dispatchers.IO) { + CatImagesService.getCatImage()[0] // TODO wrapper + } + val catResult = Cat(factJob.await().fact, imageJob.await().url) + Log.d("TAG", "Fact is ${catResult.fact}, image is ${catResult.imageUrl}") + _catsView?.populate(catResult) + } catch (sockExcept: java.net.SocketTimeoutException) { + Log.d("TAG", "SockExcept") + _catsView?.showErrorToast("Не удалось получить ответ от сервера") + } catch (e: Exception) { + _catsView?.showErrorToast("${e.message}") + Log.d("TAG", "${e.message}") + CrashMonitor.trackWarning() + } + } + } + + fun attachView(catsView: ICatsView) { + _catsView = catsView + } + + fun detachView() { + _catsView = null + } + + fun cancelCoroutine() { + presenterScope.cancel() // cancel job + all children + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CatsView.kt b/app/src/main/java/otus/homework/coroutines/presentation/CatsView.kt similarity index 58% rename from app/src/main/java/otus/homework/coroutines/CatsView.kt rename to app/src/main/java/otus/homework/coroutines/presentation/CatsView.kt index fc715fd0..a2e8ab09 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsView.kt +++ b/app/src/main/java/otus/homework/coroutines/presentation/CatsView.kt @@ -1,11 +1,17 @@ -package otus.homework.coroutines +package otus.homework.coroutines.presentation import android.content.Context import android.util.AttributeSet import android.widget.Button +import android.widget.ImageView import android.widget.TextView import android.widget.Toast import androidx.constraintlayout.widget.ConstraintLayout +import com.squareup.picasso.Picasso +import otus.homework.coroutines.Cat +import otus.homework.coroutines.domain.CatsPresenter +import otus.homework.coroutines.Fact +import otus.homework.coroutines.R class CatsView @JvmOverloads constructor( context: Context, @@ -13,7 +19,7 @@ class CatsView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr), ICatsView { - var presenter :CatsPresenter? = null + var presenter : CatsPresenter? = null override fun onFinishInflate() { super.onFinishInflate() @@ -22,8 +28,10 @@ class CatsView @JvmOverloads constructor( } } - override fun populate(fact: Fact) { - findViewById(R.id.fact_textView).text = fact.fact + override fun populate(cat: Cat) { + findViewById(R.id.fact_textView).text = cat.fact + val imageView = findViewById(R.id.imageView) + Picasso.get().load(cat.imageUrl).into(imageView) } override fun showErrorToast(errorMsg: String) { @@ -33,7 +41,7 @@ class CatsView @JvmOverloads constructor( interface ICatsView { - fun populate(fact: Fact) + fun populate(cat: Cat) fun showErrorToast(errorMsg: String) } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/MainActivity.kt b/app/src/main/java/otus/homework/coroutines/presentation/MainActivity.kt similarity index 67% rename from app/src/main/java/otus/homework/coroutines/MainActivity.kt rename to app/src/main/java/otus/homework/coroutines/presentation/MainActivity.kt index 992390f5..cfea3be0 100644 --- a/app/src/main/java/otus/homework/coroutines/MainActivity.kt +++ b/app/src/main/java/otus/homework/coroutines/presentation/MainActivity.kt @@ -1,8 +1,10 @@ -package otus.homework.coroutines +package otus.homework.coroutines.presentation import androidx.appcompat.app.AppCompatActivity import android.os.Bundle -import android.util.Log +import otus.homework.coroutines.domain.CatsPresenter +import otus.homework.coroutines.DiContainer +import otus.homework.coroutines.R class MainActivity : AppCompatActivity() { @@ -16,7 +18,11 @@ class MainActivity : AppCompatActivity() { val view = layoutInflater.inflate(R.layout.activity_main, null) as CatsView setContentView(view) - catsPresenter = CatsPresenter(diContainer.service, diContainer.presenterScope) + catsPresenter = CatsPresenter( + diContainer.catFactsService, + diContainer.catImagesService, + diContainer.presenterScope + ) view.presenter = catsPresenter catsPresenter.attachView(view) catsPresenter.onInitComplete() diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9508066d..cf5c9c9c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,11 +1,11 @@ - + tools:context=".presentation.MainActivity"> - \ No newline at end of file + + + \ No newline at end of file From ca61d9be5e8a4dab02f28e838fc15d8fd2a44783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=80=D0=B8=D1=81=D0=BE=D0=B2=D1=81=D0=BA?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=90=D0=BB=D0=B5=D0=BD=D0=B0?= Date: Tue, 10 Oct 2023 18:10:03 +0300 Subject: [PATCH 3/4] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D1=8C=20=D1=80=D0=B5=D1=88=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20ViewModel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 22 ++++--- app/build.gradle | 2 + .../otus/homework/coroutines/CrashMonitor.kt | 13 ---- .../coroutines/data/CatFactsService.kt | 3 +- .../coroutines/data/CatImagesService.kt | 3 +- .../coroutines/{ => di}/DiContainer.kt | 7 +-- .../coroutines/domain/CatRepository.kt | 10 --- .../coroutines/domain/CatsPresenter.kt | 57 ----------------- .../coroutines/domain/CrashMonitor.kt | 13 ++++ .../homework/coroutines/{ => models}/Cat.kt | 2 +- .../homework/coroutines/{ => models}/Fact.kt | 2 +- .../homework/coroutines/{ => models}/Image.kt | 2 +- .../otus/homework/coroutines/models/Result.kt | 9 +++ .../coroutines/presentation/CatsView.kt | 22 ++++--- .../coroutines/presentation/MainActivity.kt | 52 +++++++++++----- .../presentation/vm/CatsViewModel.kt | 61 +++++++++++++++++++ 16 files changed, 155 insertions(+), 125 deletions(-) delete mode 100644 app/src/main/java/otus/homework/coroutines/CrashMonitor.kt rename app/src/main/java/otus/homework/coroutines/{ => di}/DiContainer.kt (76%) delete mode 100644 app/src/main/java/otus/homework/coroutines/domain/CatRepository.kt delete mode 100644 app/src/main/java/otus/homework/coroutines/domain/CatsPresenter.kt create mode 100644 app/src/main/java/otus/homework/coroutines/domain/CrashMonitor.kt rename app/src/main/java/otus/homework/coroutines/{ => models}/Cat.kt (61%) rename app/src/main/java/otus/homework/coroutines/{ => models}/Fact.kt (80%) rename app/src/main/java/otus/homework/coroutines/{ => models}/Image.kt (87%) create mode 100644 app/src/main/java/otus/homework/coroutines/models/Result.kt create mode 100644 app/src/main/java/otus/homework/coroutines/presentation/vm/CatsViewModel.kt diff --git a/README.md b/README.md index 8b5decd7..d011e0af 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,22 @@ ### Перейти с коллбеков на саспенд функции и корутины -1. Поменять возвращаемый тип в `CatsService` и добавить модификатор `suspend` -2. Переписать логику в презентере с `Callback` на корутины и `suspend` функции -3. Реализовать свой скоуп: PresenterScope с `MainDispatcher` и CoroutineName("CatsCoroutine") в качестве элементов контекста -4. Добавить обработку исключений через try-catch. В случае `java.net.SocketTimeoutException` показываем Toast с текстом "Не удалось получить ответ от сервером". В остальных случаях логируем исключение в `otus.homework.coroutines.CrashMonitor` и показываем Toast с `exception.message` -5. Не забываем отменять Job в `onStop()` ++1. Поменять возвращаемый тип в `CatFactsService` и добавить модификатор `suspend` ++2. Переписать логику в презентере с `Callback` на корутины и `suspend` функции ++3. Реализовать свой скоуп: PresenterScope с `MainDispatcher` и CoroutineName("CatsCoroutine") в качестве элементов контекста ++4. Добавить обработку исключений через try-catch. В случае `java.net.SocketTimeoutException` показываем Toast с текстом "Не удалось получить ответ от сервером". В остальных случаях логируем исключение в `otus.homework.coroutines.CrashMonitor` и показываем Toast с `exception.message` ++5. Не забываем отменять Job в `onStop()` ### Добавить к запросу фактов запрос рандомных картинок с [https://aws.random.cat/meow](https://aws.random.cat/meow) -1. На каждый рефреш экрана должен запрашиваться факт + картинка: добавляем сетевой запрос и реализуем логику аналогичную первой задаче. Для загрузки изображений уже подключена библиотека [Picasso](https://github.com/square/picasso) -2. В метод `view.populate` передаем 1 аргумент, поэтому необходимо реализовать модель презентейшен слоя в которой будут содержаться необходимые данные для рендеринга(текст и ссылка на картинку) -3. Отменятся запросы должны одновременно ++1. На каждый рефреш экрана должен запрашиваться факт + картинка: добавляем сетевой запрос и реализуем логику аналогичную первой задаче. Для загрузки изображений уже подключена библиотека [Picasso](https://github.com/square/picasso) ++2. В метод `view.populate` передаем 1 аргумент, поэтому необходимо реализовать модель презентейшен слоя в которой будут содержаться необходимые данные для рендеринга(текст и ссылка на картинку) +Оптимизируем: конвертер добавим, запустим запросы в сеть одновременно, одновременно попробуем джобы отменить +Для этого сделаем async запуск 2 корутин ++5. Отменятся запросы должны одновременно ### Реализовать решение ViewModel -1. Реализовать наследника `ViewModel` и продублировать в нем логику из `CatsPresenter`, с необходимыми изменениями. Используйте `viewModelScope` в качестве скоупа. -2. Добавить логирование ошибок через CoroutineExceptionHanlder. Используйте класс CrashMonitor в качестве фейкового CrashMonitor инструмента ++1. Реализовать наследника `ViewModel` и продублировать в нем логику из `CatsPresenter`, с необходимыми изменениями. Используйте `viewModelScope` в качестве скоупа. ++2. Добавить логирование ошибок через CoroutineExceptionHanlder. Используйте класс CrashMonitor в качестве фейкового CrashMonitor инструмента 3. Создать sealed класс `Result`. Унаследовать от него классы `Success`, `Error`. Использовать эти классы как стейт необходимый для рендеринга/отображени ошибки diff --git a/app/build.gradle b/app/build.gradle index df872a07..7c1641c0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,6 +36,8 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' + implementation 'androidx.fragment:fragment-ktx:1.3.6' implementation 'androidx.core:core-ktx:1.3.2' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' diff --git a/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt b/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt deleted file mode 100644 index b74fa4b6..00000000 --- a/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt +++ /dev/null @@ -1,13 +0,0 @@ -package otus.homework.coroutines - -import android.util.Log - -object CrashMonitor { - - /** - * Pretend this is Crashlytics/AppCenter - */ - fun trackWarning() { - Log.d("TAG", "Crash logged") - } -} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/data/CatFactsService.kt b/app/src/main/java/otus/homework/coroutines/data/CatFactsService.kt index a6ed0913..02fc8aa4 100644 --- a/app/src/main/java/otus/homework/coroutines/data/CatFactsService.kt +++ b/app/src/main/java/otus/homework/coroutines/data/CatFactsService.kt @@ -1,6 +1,7 @@ package otus.homework.coroutines.data -import otus.homework.coroutines.Fact +import otus.homework.coroutines.models.Fact +import otus.homework.coroutines.models.Result import retrofit2.http.GET interface CatFactsService { diff --git a/app/src/main/java/otus/homework/coroutines/data/CatImagesService.kt b/app/src/main/java/otus/homework/coroutines/data/CatImagesService.kt index 08324e43..f33f2308 100644 --- a/app/src/main/java/otus/homework/coroutines/data/CatImagesService.kt +++ b/app/src/main/java/otus/homework/coroutines/data/CatImagesService.kt @@ -1,6 +1,7 @@ package otus.homework.coroutines.data -import otus.homework.coroutines.Image +import otus.homework.coroutines.models.Image +import otus.homework.coroutines.models.Result import retrofit2.http.GET interface CatImagesService { diff --git a/app/src/main/java/otus/homework/coroutines/DiContainer.kt b/app/src/main/java/otus/homework/coroutines/di/DiContainer.kt similarity index 76% rename from app/src/main/java/otus/homework/coroutines/DiContainer.kt rename to app/src/main/java/otus/homework/coroutines/di/DiContainer.kt index 9b5efc8a..59ae1875 100644 --- a/app/src/main/java/otus/homework/coroutines/DiContainer.kt +++ b/app/src/main/java/otus/homework/coroutines/di/DiContainer.kt @@ -1,8 +1,5 @@ -package otus.homework.coroutines +package otus.homework.coroutines.di -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import otus.homework.coroutines.data.CatFactsService import otus.homework.coroutines.data.CatImagesService import retrofit2.Retrofit @@ -26,8 +23,6 @@ class DiContainer { .create(CatImagesService::class.java) } - val presenterScope by lazy { CoroutineScope(Dispatchers.Main + CoroutineName("CatsCoroutine")) } - companion object { private const val FACT_URL = "https://catfact.ninja/" private const val IMAGE_URL = "https://api.thecatapi.com/v1/images/" diff --git a/app/src/main/java/otus/homework/coroutines/domain/CatRepository.kt b/app/src/main/java/otus/homework/coroutines/domain/CatRepository.kt deleted file mode 100644 index 9446b831..00000000 --- a/app/src/main/java/otus/homework/coroutines/domain/CatRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package otus.homework.coroutines.domain - -import otus.homework.coroutines.data.CatFactsService -import otus.homework.coroutines.data.CatImagesService - -class CatRepository( - private val catFactsService: CatFactsService, - private val catImagesService: CatImagesService -) { -} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/domain/CatsPresenter.kt b/app/src/main/java/otus/homework/coroutines/domain/CatsPresenter.kt deleted file mode 100644 index e69537e1..00000000 --- a/app/src/main/java/otus/homework/coroutines/domain/CatsPresenter.kt +++ /dev/null @@ -1,57 +0,0 @@ -package otus.homework.coroutines.domain - -import android.util.Log -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch -import otus.homework.coroutines.Cat -import otus.homework.coroutines.CrashMonitor -import otus.homework.coroutines.data.CatFactsService -import otus.homework.coroutines.data.CatImagesService -import otus.homework.coroutines.presentation.ICatsView - -class CatsPresenter( - private val CatFactsService: CatFactsService, - private val CatImagesService: CatImagesService, - private val presenterScope: CoroutineScope -) { - - private var _catsView: ICatsView? = null - - fun onInitComplete() { - presenterScope.launch { - try { - val factJob = async(Dispatchers.IO) { - CatFactsService.getCatFact() - } - val imageJob = async(Dispatchers.IO) { - CatImagesService.getCatImage()[0] // TODO wrapper - } - val catResult = Cat(factJob.await().fact, imageJob.await().url) - Log.d("TAG", "Fact is ${catResult.fact}, image is ${catResult.imageUrl}") - _catsView?.populate(catResult) - } catch (sockExcept: java.net.SocketTimeoutException) { - Log.d("TAG", "SockExcept") - _catsView?.showErrorToast("Не удалось получить ответ от сервера") - } catch (e: Exception) { - _catsView?.showErrorToast("${e.message}") - Log.d("TAG", "${e.message}") - CrashMonitor.trackWarning() - } - } - } - - fun attachView(catsView: ICatsView) { - _catsView = catsView - } - - fun detachView() { - _catsView = null - } - - fun cancelCoroutine() { - presenterScope.cancel() // cancel job + all children - } -} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/domain/CrashMonitor.kt b/app/src/main/java/otus/homework/coroutines/domain/CrashMonitor.kt new file mode 100644 index 00000000..24843216 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/domain/CrashMonitor.kt @@ -0,0 +1,13 @@ +package otus.homework.coroutines.domain + +import android.util.Log + +object CrashMonitor { + + /** + * Pretend this is Crashlytics/AppCenter + */ + fun trackWarning(warningMessage: String) { + Log.d("TAG", "Crash logged: $warningMessage") + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/Cat.kt b/app/src/main/java/otus/homework/coroutines/models/Cat.kt similarity index 61% rename from app/src/main/java/otus/homework/coroutines/Cat.kt rename to app/src/main/java/otus/homework/coroutines/models/Cat.kt index 82063859..564038db 100644 --- a/app/src/main/java/otus/homework/coroutines/Cat.kt +++ b/app/src/main/java/otus/homework/coroutines/models/Cat.kt @@ -1,4 +1,4 @@ -package otus.homework.coroutines +package otus.homework.coroutines.models data class Cat( val fact: String, diff --git a/app/src/main/java/otus/homework/coroutines/Fact.kt b/app/src/main/java/otus/homework/coroutines/models/Fact.kt similarity index 80% rename from app/src/main/java/otus/homework/coroutines/Fact.kt rename to app/src/main/java/otus/homework/coroutines/models/Fact.kt index 643a5a33..c38db350 100644 --- a/app/src/main/java/otus/homework/coroutines/Fact.kt +++ b/app/src/main/java/otus/homework/coroutines/models/Fact.kt @@ -1,4 +1,4 @@ -package otus.homework.coroutines +package otus.homework.coroutines.models import com.google.gson.annotations.SerializedName diff --git a/app/src/main/java/otus/homework/coroutines/Image.kt b/app/src/main/java/otus/homework/coroutines/models/Image.kt similarity index 87% rename from app/src/main/java/otus/homework/coroutines/Image.kt rename to app/src/main/java/otus/homework/coroutines/models/Image.kt index dcc4872a..3ec4d532 100644 --- a/app/src/main/java/otus/homework/coroutines/Image.kt +++ b/app/src/main/java/otus/homework/coroutines/models/Image.kt @@ -1,4 +1,4 @@ -package otus.homework.coroutines +package otus.homework.coroutines.models import com.google.gson.annotations.SerializedName diff --git a/app/src/main/java/otus/homework/coroutines/models/Result.kt b/app/src/main/java/otus/homework/coroutines/models/Result.kt new file mode 100644 index 00000000..27ca2b01 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/models/Result.kt @@ -0,0 +1,9 @@ +package otus.homework.coroutines.models + +sealed class Result( + val data: T? = null, + val error: String? = null +) { + class Success (data: T): Result(data = data) + class Error (error: String): Result(error = error) +} diff --git a/app/src/main/java/otus/homework/coroutines/presentation/CatsView.kt b/app/src/main/java/otus/homework/coroutines/presentation/CatsView.kt index a2e8ab09..a355d6b3 100644 --- a/app/src/main/java/otus/homework/coroutines/presentation/CatsView.kt +++ b/app/src/main/java/otus/homework/coroutines/presentation/CatsView.kt @@ -2,16 +2,24 @@ package otus.homework.coroutines.presentation import android.content.Context import android.util.AttributeSet +import android.util.Log import android.widget.Button import android.widget.ImageView import android.widget.TextView import android.widget.Toast +import androidx.activity.viewModels import androidx.constraintlayout.widget.ConstraintLayout +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewTreeViewModelStoreOwner +import androidx.lifecycle.get import com.squareup.picasso.Picasso -import otus.homework.coroutines.Cat -import otus.homework.coroutines.domain.CatsPresenter -import otus.homework.coroutines.Fact +import otus.homework.coroutines.models.Cat import otus.homework.coroutines.R +import otus.homework.coroutines.presentation.vm.CatsViewModel +import otus.homework.coroutines.presentation.vm.CatsViewModelFactory class CatsView @JvmOverloads constructor( context: Context, @@ -19,12 +27,10 @@ class CatsView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr), ICatsView { - var presenter : CatsPresenter? = null - - override fun onFinishInflate() { - super.onFinishInflate() + fun setOnButtonClick(onButtonClick: () -> Unit) { findViewById