From 4d0cc9e0cece02743a719be1a9abf42d0d4944bc Mon Sep 17 00:00:00 2001 From: m_kolobanova Date: Fri, 26 Sep 2025 23:23:27 +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 1. Исправлена ошибка краща 2. Изменена реализация с `LiveData` на `StateFlow` 3. В случае если возникнет ошибка в стриме, нужно заэмитить айтем `Result.Error`* PS baseUrl("https://cat-fact.herokuapp.com/facts/") - HTTP 404, поэтому https://catfact.ninja/ --- .../otus/homework/flowcats/CatsRepository.kt | 10 +++-- .../otus/homework/flowcats/CatsService.kt | 5 ++- .../java/otus/homework/flowcats/CatsView.kt | 21 ++++++++-- .../otus/homework/flowcats/CatsViewModel.kt | 39 ++++++++++++++----- .../otus/homework/flowcats/DiContainer.kt | 3 +- .../main/java/otus/homework/flowcats/Fact.kt | 8 +++- .../otus/homework/flowcats/MainActivity.kt | 12 ++++-- 7 files changed, 75 insertions(+), 23 deletions(-) diff --git a/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt index 10fcb77d..aef27200 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt @@ -10,9 +10,13 @@ class CatsRepository( fun listenForCatFacts() = flow { while (true) { - val latestNews = catsService.getCatFact() - emit(latestNews) - delay(refreshIntervalMs) + try { + val latestNews = catsService.getCatFact() + emit(Result.Success(latestNews)) + delay(refreshIntervalMs) + } catch (e: Exception) { + emit(Result.Error(e.message)) + } } } } \ No newline at end of file diff --git a/flowcats/src/main/java/otus/homework/flowcats/CatsService.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsService.kt index 25192882..f8e78d30 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsService.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsService.kt @@ -4,6 +4,9 @@ import retrofit2.http.GET interface CatsService { - @GET("random?animal_type=cat") + /*@GET("random?animal_type=cat") + suspend fun getCatFact(): Fact*/ + + @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..f1d822e0 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsView.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsView.kt @@ -3,6 +3,7 @@ package otus.homework.flowcats import android.content.Context import android.util.AttributeSet import android.widget.TextView +import android.widget.Toast import androidx.constraintlayout.widget.ConstraintLayout class CatsView @JvmOverloads constructor( @@ -11,12 +12,26 @@ class CatsView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr), ICatsView { - override fun populate(fact: Fact) { - findViewById(R.id.fact_textView).text = fact.text + override fun populate(result: Result) { + when (result) { + is Result.Success<*> -> { + if (result.data is Fact) { + findViewById(R.id.fact_textView).text = result.data.fact + } + } + + is Result.Error -> { + Toast.makeText(context, result.msg, Toast.LENGTH_SHORT).show() + } + + else -> { + //DO nothing + } + } } } interface ICatsView { - fun populate(fact: Fact) + fun populate(result: Result) } \ No newline at end of file diff --git a/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt index 0d8ba8a7..ee70a4f1 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt @@ -2,30 +2,49 @@ package otus.homework.flowcats import androidx.lifecycle.* 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.collect +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext class CatsViewModel( private val catsRepository: CatsRepository ) : ViewModel() { - private val _catsLiveData = MutableLiveData() - val catsLiveData: LiveData = _catsLiveData + //private val _catsLiveData = MutableStateFlow(Fact("", false, "0", "", "", false, "", "", "")) + private val _catsLiveData = MutableStateFlow(value = Result.Success(Fact("", 0))) + val catsFlowData: StateFlow = _catsLiveData.asStateFlow() init { viewModelScope.launch { - withContext(Dispatchers.IO) { - catsRepository.listenForCatFacts().collect { - _catsLiveData.value = it + catsRepository.listenForCatFacts() + .flowOn(Dispatchers.IO) + .catch { e -> + _catsLiveData.emit(Result.Error(e.message.toString())) + } + .collect { + _catsLiveData.emit(it) } - } } } } class CatsViewModelFactory(private val catsRepository: CatsRepository) : ViewModelProvider.NewInstanceFactory() { - override fun create(modelClass: Class): T = - CatsViewModel(catsRepository) as T -} \ No newline at end of file + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(CatsViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return CatsViewModel(catsRepository) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} + +sealed class Result { + data class Success(val data: T) : Result() + data class Error(val msg: String?) : Result() + +} diff --git a/flowcats/src/main/java/otus/homework/flowcats/DiContainer.kt b/flowcats/src/main/java/otus/homework/flowcats/DiContainer.kt index 485152e2..402752b0 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..7cd73e6d 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/Fact.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/Fact.kt @@ -3,7 +3,7 @@ package otus.homework.flowcats import com.google.gson.annotations.SerializedName data class Fact( - @field:SerializedName("createdAt") + /*@field:SerializedName("createdAt") val createdAt: String, @field:SerializedName("deleted") val deleted: Boolean, @@ -20,5 +20,9 @@ data class Fact( @field:SerializedName("user") val user: String, @field:SerializedName("updatedAt") - val updatedAt: String + val updatedAt: String*/ + @field:SerializedName("fact") + val fact: String, + @field:SerializedName("length") + val length: Int ) \ 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..3ddff8b5 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt @@ -3,19 +3,25 @@ package otus.homework.flowcats import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import otus.homework.flowcats.Result.Success class MainActivity : AppCompatActivity() { private val diContainer = DiContainer() private val catsViewModel by viewModels { CatsViewModelFactory(diContainer.repository) } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val view = layoutInflater.inflate(R.layout.activity_main, null) as CatsView setContentView(view) - - catsViewModel.catsLiveData.observe(this){ - view.populate(it) + lifecycleScope.launch { + catsViewModel.catsFlowData.collectLatest { + view.populate(it) + } } } } \ No newline at end of file From 891504dca4e828cc226917a87e793f3c2b6e811d Mon Sep 17 00:00:00 2001 From: m_kolobanova Date: Fri, 26 Sep 2025 23:59:30 +0300 Subject: [PATCH 2/4] =?UTF-8?q?=D0=92=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81?= =?UTF-8?q?=D0=B5=20`SampleInteractor`=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BB=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20`task1`-`task4`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otus/homework/flow/SampleInteractor.kt | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/operators/src/main/java/otus/homework/flow/SampleInteractor.kt b/operators/src/main/java/otus/homework/flow/SampleInteractor.kt index 1993c064..26040e45 100644 --- a/operators/src/main/java/otus/homework/flow/SampleInteractor.kt +++ b/operators/src/main/java/otus/homework/flow/SampleInteractor.kt @@ -1,5 +1,6 @@ package otus.homework.flow +import android.widget.Space import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* @@ -18,7 +19,16 @@ class SampleInteractor( * 6) возвращает результат */ fun task1(): Flow { - return flowOf() + return sampleRepository.produceNumbers() + .map { + it.times(5) + }.filter { + it > 20 + }.filter { + it % 2 != 0 + }.map { + "$it won" + }.take(3) } /** @@ -29,7 +39,27 @@ class SampleInteractor( * Если число не делится на 3,5,15 - эмитим само число */ fun task2(): Flow { - return flowOf() + return sampleRepository.produceNumbers() + .transform { + when { + it % 15 == 0 -> { + emit(it.toString()) + emit("FizzBuzz") + } + + it % 3 == 0 -> { + emit(it.toString()) + emit("Fizz") + } + + it % 5 == 0 -> { + emit(it.toString()) + emit("Buzz") + } + + else -> emit(it.toString()) + } + } } /** @@ -38,7 +68,10 @@ class SampleInteractor( * Если айтемы в одно из флоу кончились то результирующий флоу также должен закончится */ fun task3(): Flow> { - return flowOf() + return sampleRepository.produceColors() + .zip(sampleRepository.produceForms()) { color, form -> + Pair(color, form) + } } /** @@ -48,6 +81,15 @@ class SampleInteractor( * При любом исходе, будь то выброс исключения или успешная отработка функции вызовите метод dotsRepository.completed() */ fun task4(): Flow { - return flowOf() + return sampleRepository.produceNumbers() + .catch { + if (it is IllegalArgumentException) { + emit(-1) + } else { + throw it + } + }.onCompletion { + sampleRepository.completed() + } } } \ No newline at end of file From 3ebfb659dab72c469096a81af40400256911f7a6 Mon Sep 17 00:00:00 2001 From: m_kolobanova Date: Sat, 4 Oct 2025 16:29:25 +0300 Subject: [PATCH 3/4] CodeReview fixed: - add repeatOnLifecycle --- flowcats/build.gradle | 6 ++++-- flowcats/src/main/AndroidManifest.xml | 6 ++++-- .../main/java/otus/homework/flowcats/CatsRepository.kt | 2 +- .../src/main/java/otus/homework/flowcats/CatsView.kt | 5 ----- .../src/main/java/otus/homework/flowcats/MainActivity.kt | 9 +++++++-- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/flowcats/build.gradle b/flowcats/build.gradle index 0ea35e68..74e85716 100644 --- a/flowcats/build.gradle +++ b/flowcats/build.gradle @@ -4,7 +4,7 @@ plugins { } android { - compileSdkVersion 30 + compileSdkVersion 34 buildToolsVersion "30.0.3" namespace = "otus.homework.flowcats" @@ -12,7 +12,8 @@ android { defaultConfig { applicationId "otus.homework.flowcats" minSdkVersion 23 - targetSdkVersion 30 + compileSdkVersion 34 + targetSdkVersion 34 versionCode 1 versionName "1.0" @@ -48,4 +49,5 @@ dependencies { 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.4.0' } \ No newline at end of file diff --git a/flowcats/src/main/AndroidManifest.xml b/flowcats/src/main/AndroidManifest.xml index 2deb6454..76e5115c 100644 --- a/flowcats/src/main/AndroidManifest.xml +++ b/flowcats/src/main/AndroidManifest.xml @@ -9,8 +9,10 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.Flow" > - + android:theme="@style/Theme.Flow"> + diff --git a/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt index aef27200..0a8aec63 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt @@ -13,10 +13,10 @@ class CatsRepository( try { val latestNews = catsService.getCatFact() emit(Result.Success(latestNews)) - delay(refreshIntervalMs) } catch (e: Exception) { emit(Result.Error(e.message)) } + delay(refreshIntervalMs) } } } \ 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 f1d822e0..75a7f72f 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsView.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsView.kt @@ -19,14 +19,9 @@ class CatsView @JvmOverloads constructor( findViewById(R.id.fact_textView).text = result.data.fact } } - is Result.Error -> { Toast.makeText(context, result.msg, Toast.LENGTH_SHORT).show() } - - else -> { - //DO nothing - } } } } diff --git a/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt b/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt index 3ddff8b5..78ec2212 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt @@ -3,7 +3,9 @@ package otus.homework.flowcats import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.activity.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import otus.homework.flowcats.Result.Success @@ -18,9 +20,12 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) val view = layoutInflater.inflate(R.layout.activity_main, null) as CatsView setContentView(view) + lifecycleScope.launch { - catsViewModel.catsFlowData.collectLatest { - view.populate(it) + repeatOnLifecycle(Lifecycle.State.STARTED){ + catsViewModel.catsFlowData.collectLatest { + view.populate(it) + } } } } From 8221ecf4dc1641d7df2f668e6603f41b04630aa5 Mon Sep 17 00:00:00 2001 From: m_kolobanova Date: Sat, 4 Oct 2025 16:31:31 +0300 Subject: [PATCH 4/4] Delete unused import --- flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt b/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt index 78ec2212..67a360d9 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt @@ -8,7 +8,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -import otus.homework.flowcats.Result.Success + class MainActivity : AppCompatActivity() {