From 8c6dc18fe10489c072ff811c1b09a3360080f9b1 Mon Sep 17 00:00:00 2001 From: Petr Date: Thu, 23 Jan 2025 23:23:27 +0400 Subject: [PATCH 1/3] =?UTF-8?q?###=20=D0=9F=D0=B5=D1=80=D0=B5=D0=B9=D1=82?= =?UTF-8?q?=D0=B8=20=D1=81=20=D0=BA=D0=BE=D0=BB=D0=BB=D0=B1=D0=B5=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=BD=D0=B0=20=D1=81=D0=B0=D1=81=D0=BF=D0=B5?= =?UTF-8?q?=D0=BD=D0=B4=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B8=20=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 --- flowcats/build.gradle | 8 ++--- .../flowcats/ExampleInstrumentedTest.kt | 24 --------------- flowcats/src/main/AndroidManifest.xml | 8 +++-- .../otus/homework/flowcats/CatsRepository.kt | 12 ++++++-- .../otus/homework/flowcats/CatsService.kt | 2 +- .../java/otus/homework/flowcats/CatsView.kt | 2 +- .../otus/homework/flowcats/CatsViewModel.kt | 30 ++++++++++++++----- .../otus/homework/flowcats/DiContainer.kt | 2 +- .../main/java/otus/homework/flowcats/Fact.kt | 22 +++----------- .../otus/homework/flowcats/MainActivity.kt | 29 ++++++++++++++++-- .../java/otus/homework/flowcats/Result.kt | 11 +++++++ flowcats/src/main/res/values/strings.xml | 1 + .../main/res/xml/network_security_config.xml | 9 ++++++ .../homework/flow/ExampleInstrumentedTest.kt | 24 --------------- operators/src/main/AndroidManifest.xml | 4 +-- 15 files changed, 95 insertions(+), 93 deletions(-) delete mode 100644 flowcats/src/androidTest/java/otus/homework/flowcats/ExampleInstrumentedTest.kt create mode 100644 flowcats/src/main/java/otus/homework/flowcats/Result.kt create mode 100644 flowcats/src/main/res/xml/network_security_config.xml delete mode 100644 operators/src/androidTest/java/otus/homework/flow/ExampleInstrumentedTest.kt diff --git a/flowcats/build.gradle b/flowcats/build.gradle index 0ea35e68..a6ce9085 100644 --- a/flowcats/build.gradle +++ b/flowcats/build.gradle @@ -4,15 +4,15 @@ plugins { } android { - compileSdkVersion 30 + compileSdkVersion 34 buildToolsVersion "30.0.3" namespace = "otus.homework.flowcats" defaultConfig { applicationId "otus.homework.flowcats" - minSdkVersion 23 - targetSdkVersion 30 + minSdkVersion 24 + targetSdkVersion 34 versionCode 1 versionName "1.0" @@ -47,5 +47,5 @@ dependencies { 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-viewmodel-ktx:2.8.7' } \ No newline at end of file diff --git a/flowcats/src/androidTest/java/otus/homework/flowcats/ExampleInstrumentedTest.kt b/flowcats/src/androidTest/java/otus/homework/flowcats/ExampleInstrumentedTest.kt deleted file mode 100644 index fd17ff6f..00000000 --- a/flowcats/src/androidTest/java/otus/homework/flowcats/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package otus.homework.flowcats - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("otus.homework.flowcats", appContext.packageName) - } -} \ No newline at end of file diff --git a/flowcats/src/main/AndroidManifest.xml b/flowcats/src/main/AndroidManifest.xml index 2deb6454..ecd36a45 100644 --- a/flowcats/src/main/AndroidManifest.xml +++ b/flowcats/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + - + diff --git a/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt index 10fcb77d..e24d36b5 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt @@ -1,6 +1,8 @@ package otus.homework.flowcats +import MyResult import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow class CatsRepository( @@ -8,10 +10,14 @@ class CatsRepository( private val refreshIntervalMs: Long = 5000 ) { - fun listenForCatFacts() = flow { + fun listenForCatFacts(): Flow> = flow { while (true) { - val latestNews = catsService.getCatFact() - emit(latestNews) + val factCat = try { + MyResult.Success(catsService.getCatFact()) + } catch (t: Throwable) { + MyResult.Error(errorMsg = t.message) + } + emit(factCat) delay(refreshIntervalMs) } } diff --git a/flowcats/src/main/java/otus/homework/flowcats/CatsService.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsService.kt index 25192882..787829a1 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsService.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsService.kt @@ -4,6 +4,6 @@ import retrofit2.http.GET interface CatsService { - @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..b2b6b0db 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt @@ -1,8 +1,13 @@ package otus.homework.flowcats -import androidx.lifecycle.* +import MyResult +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -10,15 +15,23 @@ class CatsViewModel( private val catsRepository: CatsRepository ) : ViewModel() { - private val _catsLiveData = MutableLiveData() - val catsLiveData: LiveData = _catsLiveData + private val _catFact: MutableStateFlow> = MutableStateFlow( + MyResult.Success( + Fact( + fact = "", + length = 0 + ) + ) + ) + val catFact: StateFlow> = _catFact.asStateFlow() init { viewModelScope.launch { withContext(Dispatchers.IO) { - catsRepository.listenForCatFacts().collect { - _catsLiveData.value = it - } + catsRepository.listenForCatFacts() + .collect { + _catFact.value = it + } } } } @@ -26,6 +39,7 @@ class CatsViewModel( class CatsViewModelFactory(private val catsRepository: CatsRepository) : ViewModelProvider.NewInstanceFactory() { - override fun create(modelClass: Class): T = + @Suppress("UNCHECKED_CAST") + 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..479958b5 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/DiContainer.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/DiContainer.kt @@ -7,7 +7,7 @@ class DiContainer { private val retrofit by lazy { Retrofit.Builder() - .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..81998454 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/Fact.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/Fact.kt @@ -3,22 +3,8 @@ 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, + @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..4d99bc55 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt @@ -1,8 +1,14 @@ package otus.homework.flowcats -import androidx.appcompat.app.AppCompatActivity +import MyResult 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 +20,25 @@ 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.catFact.collect { result -> + when (result) { + is MyResult.Success -> view.populate(result.data) + is MyResult.Error -> { + val errorMessage = when { + result.errorResId != null -> this@MainActivity.getText(result.errorResId) + result.errorMsg != null -> result.errorMsg + else -> this@MainActivity.getText(R.string.app_unknown_error) + } + Toast.makeText(this@MainActivity, errorMessage, Toast.LENGTH_LONG) + .show() + } + + } + + } + } } } } \ No newline at end of file diff --git a/flowcats/src/main/java/otus/homework/flowcats/Result.kt b/flowcats/src/main/java/otus/homework/flowcats/Result.kt new file mode 100644 index 00000000..aedfa920 --- /dev/null +++ b/flowcats/src/main/java/otus/homework/flowcats/Result.kt @@ -0,0 +1,11 @@ +import androidx.annotation.StringRes + +sealed class MyResult { + + class Success(val data: T) : MyResult() + + class Error( + @StringRes val errorResId: Int? = null, + val errorMsg: String? = null + ) : MyResult() +} \ No newline at end of file diff --git a/flowcats/src/main/res/values/strings.xml b/flowcats/src/main/res/values/strings.xml index ba737b82..63cbb21c 100644 --- a/flowcats/src/main/res/values/strings.xml +++ b/flowcats/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ Flow cats + Unknown error \ No newline at end of file diff --git a/flowcats/src/main/res/xml/network_security_config.xml b/flowcats/src/main/res/xml/network_security_config.xml new file mode 100644 index 00000000..2e8fae1b --- /dev/null +++ b/flowcats/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/operators/src/androidTest/java/otus/homework/flow/ExampleInstrumentedTest.kt b/operators/src/androidTest/java/otus/homework/flow/ExampleInstrumentedTest.kt deleted file mode 100644 index 2a37f840..00000000 --- a/operators/src/androidTest/java/otus/homework/flow/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package otus.homework.flow - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("otus.homework.flow", appContext.packageName) - } -} \ No newline at end of file diff --git a/operators/src/main/AndroidManifest.xml b/operators/src/main/AndroidManifest.xml index b8ab777d..51948c58 100644 --- a/operators/src/main/AndroidManifest.xml +++ b/operators/src/main/AndroidManifest.xml @@ -1,7 +1,5 @@ - + Date: Fri, 24 Jan 2025 00:12:24 +0400 Subject: [PATCH 2/3] =?UTF-8?q?###=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=84=D1=83=D0=BD=D0=BA=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20flow=20=D0=BE?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B0=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otus/homework/flow/SampleInteractor.kt | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 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..804efd1f 100644 --- a/operators/src/main/java/otus/homework/flow/SampleInteractor.kt +++ b/operators/src/main/java/otus/homework/flow/SampleInteractor.kt @@ -1,7 +1,15 @@ package otus.homework.flow import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.transform +import kotlinx.coroutines.flow.zip @ExperimentalCoroutinesApi class SampleInteractor( @@ -18,7 +26,12 @@ class SampleInteractor( * 6) возвращает результат */ fun task1(): Flow { - return flowOf() + return sampleRepository.produceNumbers() + .map { it * 5 } + .filterNot { it <= 20 } + .filter { it % 2 == 1 } + .map { "$it won" } + .take(3) } /** @@ -29,7 +42,14 @@ class SampleInteractor( * Если число не делится на 3,5,15 - эмитим само число */ fun task2(): Flow { - return flowOf() + return sampleRepository.produceNumbers().transform { + emit(it.toString()) + when { + it % 15 == 0 -> emit("FizzBuzz") + it % 5 == 0 -> emit("Buzz") + it % 3 == 0 -> emit("Fizz") + } + } } /** @@ -38,7 +58,10 @@ class SampleInteractor( * Если айтемы в одно из флоу кончились то результирующий флоу также должен закончится */ fun task3(): Flow> { - return flowOf() + return sampleRepository.produceColors() + .zip(sampleRepository.produceForms()) { color, form -> + color to form + } } /** @@ -48,6 +71,16 @@ 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 e819483c8268273bd5050f09b523823b9929e6d2 Mon Sep 17 00:00:00 2001 From: Petr Date: Fri, 24 Jan 2025 23:33:41 +0400 Subject: [PATCH 3/3] =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BA?= =?UTF-8?q?=D0=BE=D1=81=D1=8F=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/otus/homework/flowcats/CatsRepository.kt | 4 +++- .../java/otus/homework/flowcats/CatsViewModel.kt | 12 ++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt index e24d36b5..1e7c144e 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt @@ -1,9 +1,11 @@ package otus.homework.flowcats import MyResult +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn class CatsRepository( private val catsService: CatsService, @@ -20,5 +22,5 @@ class CatsRepository( emit(factCat) delay(refreshIntervalMs) } - } + }.flowOn(Dispatchers.IO) } \ 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 b2b6b0db..43448bf2 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt @@ -4,12 +4,10 @@ import MyResult 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.launch -import kotlinx.coroutines.withContext class CatsViewModel( private val catsRepository: CatsRepository @@ -27,12 +25,10 @@ class CatsViewModel( init { viewModelScope.launch { - withContext(Dispatchers.IO) { - catsRepository.listenForCatFacts() - .collect { - _catFact.value = it - } - } + catsRepository.listenForCatFacts() + .collect { + _catFact.value = it + } } } }