From 549912d189f298708be63798642fe765435d46fd Mon Sep 17 00:00:00 2001 From: AleksVira Date: Thu, 6 Nov 2025 01:32:50 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=B0=D1=8F=20?= =?UTF-8?q?=D1=87=D0=B0=D1=81=D1=82=D1=8C=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F,=20=D0=BF=D0=B5=D1=80=D0=B5=D1=85=D0=BE=D0=B4=20?= =?UTF-8?q?=D1=81=20LiveData=20=D0=BD=D0=B0=20StateFlow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- flowcats/build.gradle | 7 ++-- flowcats/src/main/AndroidManifest.xml | 3 +- .../otus/homework/flowcats/CatsService.kt | 3 +- .../java/otus/homework/flowcats/CatsView.kt | 2 +- .../otus/homework/flowcats/CatsViewModel.kt | 35 ++++++++++++------- .../otus/homework/flowcats/DiContainer.kt | 3 +- .../main/java/otus/homework/flowcats/Fact.kt | 20 ++--------- .../otus/homework/flowcats/MainActivity.kt | 22 ++++++++++-- 9 files changed, 55 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 0e62fd9e..12095b5a 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,6 @@ *Создать sealed класс `Result`. Унаследовать от него классы `Success`, `Error`. Использовать эти классы как стейт необходимый для рендеринга/отображени ошибки -### Реализовать функции с использование flow операторов +### Реализовать функции с использованием flow операторов 1. В классе `SampleInteractor` реализуйте функции `task1`-`task4` в соответствии с условиями. Для проверки функций используйте тесты в `SampleInteractorTest` diff --git a/flowcats/build.gradle b/flowcats/build.gradle index 0ea35e68..209f14cf 100644 --- a/flowcats/build.gradle +++ b/flowcats/build.gradle @@ -4,7 +4,7 @@ plugins { } android { - compileSdkVersion 30 + compileSdkVersion 36 buildToolsVersion "30.0.3" namespace = "otus.homework.flowcats" @@ -12,7 +12,7 @@ android { defaultConfig { applicationId "otus.homework.flowcats" minSdkVersion 23 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 1 versionName "1.0" @@ -46,6 +46,7 @@ dependencies { implementation 'com.squareup.picasso:picasso:2.71828' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3' implementation 'androidx.activity:activity-ktx:1.2.3' - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.9.4' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3' } \ No newline at end of file diff --git a/flowcats/src/main/AndroidManifest.xml b/flowcats/src/main/AndroidManifest.xml index 2deb6454..6531e25f 100644 --- a/flowcats/src/main/AndroidManifest.xml +++ b/flowcats/src/main/AndroidManifest.xml @@ -10,7 +10,8 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Flow" > - + diff --git a/flowcats/src/main/java/otus/homework/flowcats/CatsService.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsService.kt index 25192882..aefe965f 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsService.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsService.kt @@ -4,6 +4,7 @@ import retrofit2.http.GET interface CatsService { - @GET("random?animal_type=cat") +// @GET("random?animal_type=cat") + @GET("fact") suspend fun getCatFact(): Fact } \ No newline at end of file diff --git a/flowcats/src/main/java/otus/homework/flowcats/CatsView.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsView.kt index 6a195f3a..eba22638 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsView.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsView.kt @@ -12,7 +12,7 @@ class CatsView @JvmOverloads constructor( ) : ConstraintLayout(context, attrs, defStyleAttr), ICatsView { override fun populate(fact: Fact) { - findViewById(R.id.fact_textView).text = fact.text + findViewById(R.id.fact_textView).text = fact.fact } } diff --git a/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt index 0d8ba8a7..c43e52e3 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt @@ -1,31 +1,40 @@ package otus.homework.flowcats -import androidx.lifecycle.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collect +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext + +sealed class Result { + data class Success(val fact: Fact) : Result() + data class Error(val throwable: Throwable) : Result() +} class CatsViewModel( private val catsRepository: CatsRepository ) : ViewModel() { - private val _catsLiveData = MutableLiveData() - val catsLiveData: LiveData = _catsLiveData + private val _catsStateFlow = MutableStateFlow(null) + val catsStateFlow: StateFlow = _catsStateFlow.asStateFlow() init { viewModelScope.launch { - withContext(Dispatchers.IO) { - catsRepository.listenForCatFacts().collect { - _catsLiveData.value = it + catsRepository.listenForCatFacts() + .map { fact -> Result.Success(fact) } + .catch { e -> emit(Result.Error(e)) } + .collect { result -> + _catsStateFlow.value = result } - } } } } -class CatsViewModelFactory(private val catsRepository: CatsRepository) : - ViewModelProvider.NewInstanceFactory() { - override fun create(modelClass: Class): T = +class CatsViewModelFactory(private val catsRepository: CatsRepository) : ViewModelProvider.NewInstanceFactory() { + override fun create(modelClass: Class): T = CatsViewModel(catsRepository) as T } \ No newline at end of file diff --git a/flowcats/src/main/java/otus/homework/flowcats/DiContainer.kt b/flowcats/src/main/java/otus/homework/flowcats/DiContainer.kt index 485152e2..0acce752 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/DiContainer.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/DiContainer.kt @@ -7,7 +7,8 @@ class DiContainer { private val retrofit by lazy { Retrofit.Builder() - .baseUrl("https://cat-fact.herokuapp.com/facts/") +// .baseUrl("https://cat-fact.herokuapp.com/facts/") + .baseUrl("https://catfact.ninja/") .addConverterFactory(GsonConverterFactory.create()) .build() } diff --git a/flowcats/src/main/java/otus/homework/flowcats/Fact.kt b/flowcats/src/main/java/otus/homework/flowcats/Fact.kt index 602303eb..ced6adcc 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/Fact.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/Fact.kt @@ -3,22 +3,6 @@ package otus.homework.flowcats 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 + @field:SerializedName("fact") + val fact: String ) \ No newline at end of file diff --git a/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt b/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt index edea434b..1930faf5 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt @@ -1,8 +1,13 @@ package otus.homework.flowcats -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.widget.Toast import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { @@ -14,8 +19,19 @@ class MainActivity : AppCompatActivity() { val view = layoutInflater.inflate(R.layout.activity_main, null) as CatsView setContentView(view) - catsViewModel.catsLiveData.observe(this){ - view.populate(it) + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + catsViewModel.catsStateFlow.collect { result -> + result?.let { + when (it) { + is Result.Success -> view.populate(it.fact) + is Result.Error -> { + Toast.makeText(this@MainActivity, it.throwable.message, Toast.LENGTH_SHORT).show() + } + } + } + } + } } } } \ No newline at end of file From f944da78b5de3e72f0e91ffded5443e3f3365b29 Mon Sep 17 00:00:00 2001 From: AleksVira Date: Thu, 6 Nov 2025 01:45:01 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=92=D1=82=D0=BE=D1=80=D0=B0=D1=8F=20?= =?UTF-8?q?=D1=87=D0=B0=D1=81=D1=82=D1=8C=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F,=20=D1=81=20=D1=82=D0=B5=D1=81=D1=82=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otus/homework/flowcats/CatsViewModel.kt | 18 ++++++---- .../otus/homework/flow/SampleInteractor.kt | 36 ++++++++++++++++--- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt index c43e52e3..a54d6e86 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt @@ -3,12 +3,13 @@ package otus.homework.flowcats import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext sealed class Result { data class Success(val fact: Fact) : Result() @@ -24,12 +25,15 @@ class CatsViewModel( init { viewModelScope.launch { - catsRepository.listenForCatFacts() - .map { fact -> Result.Success(fact) } - .catch { e -> emit(Result.Error(e)) } - .collect { result -> - _catsStateFlow.value = result - } + withContext(Dispatchers.IO) { + catsRepository.listenForCatFacts() + .catch { exception -> + _catsStateFlow.value = Result.Error(exception) + } + .collect { fact -> + _catsStateFlow.value = Result.Success(fact) + } + } } } } diff --git a/operators/src/main/java/otus/homework/flow/SampleInteractor.kt b/operators/src/main/java/otus/homework/flow/SampleInteractor.kt index 1993c064..a84c934c 100644 --- a/operators/src/main/java/otus/homework/flow/SampleInteractor.kt +++ b/operators/src/main/java/otus/homework/flow/SampleInteractor.kt @@ -18,7 +18,12 @@ class SampleInteractor( * 6) возвращает результат */ fun task1(): Flow { - return flowOf() + return sampleRepository.produceNumbers() + .map { number -> number * 5 } + .filter { number -> number > 20 } + .filter { number -> number % 2 != 0 } + .map { number -> "$number won" } + .take(3) } /** @@ -29,7 +34,16 @@ class SampleInteractor( * Если число не делится на 3,5,15 - эмитим само число */ fun task2(): Flow { - return flowOf() + return sampleRepository.produceNumbers() + .transform { number -> + emit(number.toString()) + + when { + number % 15 == 0 -> emit("FizzBuzz") + number % 3 == 0 -> emit("Fizz") + number % 5 == 0 -> emit("Buzz") + } + } } /** @@ -38,7 +52,12 @@ class SampleInteractor( * Если айтемы в одно из флоу кончились то результирующий флоу также должен закончится */ fun task3(): Flow> { - return flowOf() + val flow1 = sampleRepository.produceColors() + val flow2 = sampleRepository.produceForms() + + return flow1.zip(flow2) { color, form -> + Pair(color, form) + } } /** @@ -48,6 +67,15 @@ class SampleInteractor( * При любом исходе, будь то выброс исключения или успешная отработка функции вызовите метод dotsRepository.completed() */ fun task4(): Flow { - return flowOf() + return sampleRepository.produceNumbers() + .catch { exception -> + if (exception is IllegalArgumentException) { + emit(-1) } else { + throw exception + } + } + .onCompletion { + sampleRepository.completed() + } } } \ No newline at end of file From a9621b33b96edca669135c14534b41a2074c4b94 Mon Sep 17 00:00:00 2001 From: AleksVira Date: Wed, 26 Nov 2025 02:14:18 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=94=D0=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flowcats/src/main/AndroidManifest.xml | 3 +-- .../java/otus/homework/flowcats/CatsRepository.kt | 13 +++++++++++-- .../java/otus/homework/flowcats/CatsViewModel.kt | 13 ++----------- .../java/otus/homework/flowcats/MainActivity.kt | 2 +- gradle.properties | 3 ++- operators/src/main/AndroidManifest.xml | 3 +-- 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/flowcats/src/main/AndroidManifest.xml b/flowcats/src/main/AndroidManifest.xml index 6531e25f..feb3f274 100644 --- a/flowcats/src/main/AndroidManifest.xml +++ b/flowcats/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + (val data: T) : Result() + data class Error(val throwable: Throwable) : Result() +} + class CatsRepository( private val catsService: CatsService, private val refreshIntervalMs: Long = 5000 @@ -10,8 +15,12 @@ class CatsRepository( fun listenForCatFacts() = flow { while (true) { - val latestNews = catsService.getCatFact() - emit(latestNews) + try { + val fact = catsService.getCatFact() + emit(Result.Success(fact)) + } catch (e: Exception) { + emit(Result.Error(e)) + } delay(refreshIntervalMs) } } diff --git a/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt index a54d6e86..5146f8d2 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt @@ -7,15 +7,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -sealed class Result { - data class Success(val fact: Fact) : Result() - data class Error(val throwable: Throwable) : Result() -} - class CatsViewModel( private val catsRepository: CatsRepository ) : ViewModel() { @@ -27,11 +21,8 @@ class CatsViewModel( viewModelScope.launch { withContext(Dispatchers.IO) { catsRepository.listenForCatFacts() - .catch { exception -> - _catsStateFlow.value = Result.Error(exception) - } - .collect { fact -> - _catsStateFlow.value = Result.Success(fact) + .collect { result -> + _catsStateFlow.value = result } } } diff --git a/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt b/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt index 1930faf5..7ea01547 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt @@ -24,7 +24,7 @@ class MainActivity : AppCompatActivity() { catsViewModel.catsStateFlow.collect { result -> result?.let { when (it) { - is Result.Success -> view.populate(it.fact) + is Result.Success<*> -> view.populate(it.data as Fact) is Result.Error -> { Toast.makeText(this@MainActivity, it.throwable.message, Toast.LENGTH_SHORT).show() } diff --git a/gradle.properties b/gradle.properties index 98bed167..1aec05a0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,4 +18,5 @@ android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official +org.gradle.configuration-cache=true \ No newline at end of file diff --git a/operators/src/main/AndroidManifest.xml b/operators/src/main/AndroidManifest.xml index b8ab777d..b1ee20bc 100644 --- a/operators/src/main/AndroidManifest.xml +++ b/operators/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:android="http://schemas.android.com/apk/res/android">