From f8d679bb7776e4829d25a60e191ef6cbd5bfe176 Mon Sep 17 00:00:00 2001 From: AleksVira Date: Sat, 25 Oct 2025 23:08:12 +0300 Subject: [PATCH 1/6] Some changes --- README.md | 4 ++-- app/build.gradle | 17 +++++++++-------- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e2b557fe..436e87ed 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ ### Добавить к запросу фактов запрос рандомных картинок с https://api.thecatapi.com/v1/images/search 1. На каждый рефреш экрана должен запрашиваться факт + картинка: добавляем сетевой запрос и реализуем логику аналогичную первой задаче. Для загрузки изображений уже подключена библиотека [Picasso](https://github.com/square/picasso) -2. В метод `view.populate` передаем 1 аргумент, поэтому необходимо реализовать модель презентейшен слоя в которой будут содержаться необходимые данные для рендеринга(текст и ссылка на картинку) +2. В метод `view.populate` передаем 1 аргумент, поэтому необходимо реализовать модель презентейшен слоя в которой будут содержаться необходимые данные для рендеринга (текст и ссылка на картинку) 3. Отменятся запросы должны одновременно ### Реализовать решение ViewModel 1. Реализовать наследника `ViewModel` и продублировать в нем логику из `CatsPresenter`, с необходимыми изменениями. Используйте `viewModelScope` в качестве скоупа. 2. Добавить логирование ошибок через CoroutineExceptionHanlder. Используйте класс CrashMonitor в качестве фейкового CrashMonitor инструмента -3. Создать sealed класс `Result`. Унаследовать от него классы `Success`, `Error`. Использовать эти классы как стейт необходимый для рендеринга/отображени ошибки +3. Создать sealed класс `Result`. Унаследовать от него классы `Success`, `Error`. Использовать эти классы как стейт необходимый для рендеринга/отображения ошибки diff --git a/app/build.gradle b/app/build.gradle index a414e0e8..425d1271 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ plugins { android { namespace = "otus.homework.coroutines" - compileSdkVersion 34 + compileSdkVersion 36 defaultConfig { applicationId "otus.homework.coroutines" minSdkVersion 23 @@ -32,13 +32,14 @@ android { } dependencies { + //noinspection GradleDependency implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'androidx.core:core-ktx:1.12.0' - implementation 'com.squareup.retrofit2:retrofit:2.9.0' - implementation 'com.squareup.retrofit2:converter-gson:2.9.0' - implementation 'com.google.code.gson:gson:2.10' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.11.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.core:core-ktx:1.17.0' + implementation 'com.squareup.retrofit2:retrofit:3.0.0' + implementation 'com.squareup.retrofit2:converter-gson:3.0.0' + implementation 'com.google.code.gson:gson:2.13.2' + implementation 'androidx.appcompat:appcompat:1.7.1' + implementation 'com.google.android.material:material:1.13.0' + implementation 'androidx.constraintlayout:constraintlayout:2.2.1' implementation 'com.squareup.picasso:picasso:2.71828' } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9f4fb9aa..817bd91e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,12 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.8.22" + ext.kotlin_version = '2.2.21' repositories { mavenCentral() google() } dependencies { - classpath 'com.android.tools.build:gradle:8.2.1' + classpath 'com.android.tools.build:gradle:8.13.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 32f256d6..7e162238 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Jan 15 23:17:01 GST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 22f4be894f1d26d5b7e1b747731ff6488799282b Mon Sep 17 00:00:00 2001 From: AleksVira Date: Sat, 25 Oct 2025 23:51:42 +0300 Subject: [PATCH 2/6] =?UTF-8?q?=D0=9A=D0=BE=D1=80=D1=83=D1=82=D0=B8=D0=BD?= =?UTF-8?q?=D1=8B=20+=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B2=D0=B8=D1=81=20=D0=BA=D0=B0=D1=80=D1=82=D0=B8=D0=BD?= =?UTF-8?q?=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/otus/homework/coroutines/CatImage.kt | 14 ++++++ .../homework/coroutines/CatPresentation.kt | 6 +++ .../otus/homework/coroutines/CatsPresenter.kt | 47 +++++++++++++------ .../otus/homework/coroutines/CatsService.kt | 9 +++- .../java/otus/homework/coroutines/CatsView.kt | 23 +++++++-- .../otus/homework/coroutines/DiContainer.kt | 17 ++++++- .../otus/homework/coroutines/MainActivity.kt | 2 +- app/src/main/res/layout/activity_main.xml | 9 ++++ 8 files changed, 105 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/otus/homework/coroutines/CatImage.kt create mode 100644 app/src/main/java/otus/homework/coroutines/CatPresentation.kt diff --git a/app/src/main/java/otus/homework/coroutines/CatImage.kt b/app/src/main/java/otus/homework/coroutines/CatImage.kt new file mode 100644 index 00000000..56327e04 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatImage.kt @@ -0,0 +1,14 @@ +package otus.homework.coroutines + +import com.google.gson.annotations.SerializedName + +data class CatImage( + @SerializedName("url") + val url: String, + @SerializedName("width") + val width: Int, + @SerializedName("height") + val height: Int, + @SerializedName("id") + val id: String +) \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CatPresentation.kt b/app/src/main/java/otus/homework/coroutines/CatPresentation.kt new file mode 100644 index 00000000..d9d7bc11 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatPresentation.kt @@ -0,0 +1,6 @@ +package otus.homework.coroutines + +data class CatPresentation( + val fact: String, + val imageUrl: String +) diff --git a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt b/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt index e4b05120..f42797b4 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt @@ -1,28 +1,46 @@ package otus.homework.coroutines -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response +import java.net.SocketTimeoutException +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.launch class CatsPresenter( - private val catsService: CatsService + private val catsService: CatsService, + private val catsImageService: CatsImageService ) { private var _catsView: ICatsView? = null + private val presenterJob = Job() + private val presenterScope = CoroutineScope( + Dispatchers.Main + presenterJob + CoroutineName("CatsCoroutine") + ) fun onInitComplete() { - catsService.getCatFact().enqueue(object : Callback { - - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful && response.body() != null) { - _catsView?.populate(response.body()!!) - } - } - - override fun onFailure(call: Call, t: Throwable) { + presenterScope.launch { + try { + val factDeferred = async { catsService.getCatFact() } + val imageDeferred = async { catsImageService.getRandomCatImage() } + + val fact = factDeferred.await() + val images = imageDeferred.await() + + val imageUrl = images.firstOrNull()?.url + val catPresentation = CatPresentation( + fact = fact.fact, + imageUrl = imageUrl ?: "" + ) + _catsView?.populate(catPresentation) + } catch (e: SocketTimeoutException) { + _catsView?.showError("Не удалось получить ответ от сервера") + } catch (e: Exception) { CrashMonitor.trackWarning() + _catsView?.showError(e.message ?: "Неизвестная ошибка") } - }) + } } fun attachView(catsView: ICatsView) { @@ -31,5 +49,6 @@ class CatsPresenter( fun detachView() { _catsView = null + presenterJob.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..209f60b1 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsService.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsService.kt @@ -1,10 +1,15 @@ package otus.homework.coroutines -import retrofit2.Call import retrofit2.http.GET interface CatsService { @GET("fact") - fun getCatFact() : Call + suspend fun getCatFact(): Fact +} + +interface CatsImageService { + + @GET("images/search") + suspend fun getRandomCatImage(): List } \ 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 be04b2a8..f8e1195f 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsView.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsView.kt @@ -3,8 +3,11 @@ package otus.homework.coroutines 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 class CatsView @JvmOverloads constructor( context: Context, @@ -12,7 +15,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() @@ -21,12 +24,24 @@ class CatsView @JvmOverloads constructor( } } - override fun populate(fact: Fact) { - findViewById(R.id.fact_textView).text = fact.fact + override fun populate(catPresentation: CatPresentation) { + findViewById(R.id.fact_textView).text = catPresentation.fact + + if (catPresentation.imageUrl.isNotEmpty()) { + Picasso.get() + .load(catPresentation.imageUrl) + .into(findViewById(R.id.cat_imageView)) + } } + + override fun showError(message: String) { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } + } interface ICatsView { - fun populate(fact: Fact) + fun populate(catPresentation: CatPresentation) + fun showError(message: String) } \ 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..df8e8478 100644 --- a/app/src/main/java/otus/homework/coroutines/DiContainer.kt +++ b/app/src/main/java/otus/homework/coroutines/DiContainer.kt @@ -5,6 +5,20 @@ import retrofit2.converter.gson.GsonConverterFactory class DiContainer { + private val catsFactRetrofit by lazy { + Retrofit.Builder() + .baseUrl("https://catfact.ninja/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + + private val catsImageRetrofit by lazy { + Retrofit.Builder() + .baseUrl("https://api.thecatapi.com/v1/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + private val retrofit by lazy { Retrofit.Builder() .baseUrl("https://catfact.ninja/") @@ -12,5 +26,6 @@ class DiContainer { .build() } - val service by lazy { retrofit.create(CatsService::class.java) } + val catsService by lazy { catsFactRetrofit.create(CatsService::class.java) } + val catsImageService by lazy { catsImageRetrofit.create(CatsImageService::class.java) } } \ 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..614e7593 100644 --- a/app/src/main/java/otus/homework/coroutines/MainActivity.kt +++ b/app/src/main/java/otus/homework/coroutines/MainActivity.kt @@ -15,7 +15,7 @@ class MainActivity : AppCompatActivity() { val view = layoutInflater.inflate(R.layout.activity_main, null) as CatsView setContentView(view) - catsPresenter = CatsPresenter(diContainer.service) + catsPresenter = CatsPresenter(diContainer.catsService, diContainer.catsImageService) 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..69e9a951 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,6 +7,15 @@ android:layout_height="match_parent" tools:context=".MainActivity"> + + Date: Sun, 26 Oct 2025 16:13:06 +0300 Subject: [PATCH 3/6] =?UTF-8?q?=D0=A4=D0=B8=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D1=87=D0=B0=D1=81=D1=82=D1=8C=20-=20ViewModel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 7 ++- .../java/otus/homework/coroutines/CatsView.kt | 6 ++- .../otus/homework/coroutines/CatsViewModel.kt | 51 +++++++++++++++++++ .../otus/homework/coroutines/DiContainer.kt | 7 --- .../otus/homework/coroutines/MainActivity.kt | 37 +++++++++----- .../java/otus/homework/coroutines/Result.kt | 6 +++ app/src/main/res/layout/activity_main.xml | 6 +-- 7 files changed, 94 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/otus/homework/coroutines/CatsViewModel.kt create mode 100644 app/src/main/java/otus/homework/coroutines/Result.kt diff --git a/app/build.gradle b/app/build.gradle index 425d1271..8d5fb68a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,8 +26,8 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' + kotlin { + jvmToolchain(21) } } @@ -42,4 +42,7 @@ dependencies { implementation 'com.google.android.material:material:1.13.0' implementation 'androidx.constraintlayout:constraintlayout:2.2.1' implementation 'com.squareup.picasso:picasso:2.71828' + + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.4' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.9.4' } \ 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 f8e1195f..d9a23609 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsView.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsView.kt @@ -15,12 +15,14 @@ class CatsView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr), ICatsView { - var presenter: CatsPresenter? = null + var loadAction: (() -> Unit)? = null +// var presenter: CatsPresenter? = null override fun onFinishInflate() { super.onFinishInflate() findViewById