diff --git a/build.gradle b/build.gradle index e47bb55b..4fb1f3d6 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.4.32" + ext.kotlin_version = "1.6.0" repositories { google() jcenter() diff --git a/flowcats/build.gradle b/flowcats/build.gradle index cefa21e0..1b637db6 100644 --- a/flowcats/build.gradle +++ b/flowcats/build.gradle @@ -4,13 +4,13 @@ plugins { } android { - compileSdkVersion 30 + compileSdkVersion 31 buildToolsVersion "30.0.3" defaultConfig { applicationId "otus.homework.flowcats" minSdkVersion 23 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 1 versionName "1.0" @@ -44,6 +44,6 @@ 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' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0' testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' } \ 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/CatsView.kt b/flowcats/src/main/java/otus/homework/flowcats/CatsView.kt index 6a195f3a..51578947 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsView.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsView.kt @@ -3,20 +3,26 @@ 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( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr), ICatsView { - override fun populate(fact: Fact) { - findViewById(R.id.fact_textView).text = fact.text - } + override fun populate(fact: Fact) { + findViewById(R.id.fact_textView).text = fact.text + } + + override fun showError(message: String) { + Toast.makeText(context, message, Toast.LENGTH_LONG).show() + } } interface ICatsView { - fun populate(fact: Fact) + fun populate(fact: Fact) + fun showError(message: String) } \ 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..f965309e 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt @@ -1,31 +1,43 @@ package otus.homework.flowcats -import androidx.lifecycle.* +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.catch import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class CatsViewModel( - private val catsRepository: CatsRepository + private val catsRepository: CatsRepository ) : ViewModel() { - private val _catsLiveData = MutableLiveData() - val catsLiveData: LiveData = _catsLiveData + private val _catsState = MutableStateFlow>(Success.EMPTY) + val catsState: StateFlow> = _catsState - init { - viewModelScope.launch { - withContext(Dispatchers.IO) { - catsRepository.listenForCatFacts().collect { - _catsLiveData.value = it - } - } - } + init { + viewModelScope.launch { + withContext(Dispatchers.IO) { + catsRepository.listenForCatFacts() + .catch { exception -> _catsState.value = Error(exception.message ?: "Flow Error") } + .collect { fact -> + _catsState.value = Success(fact) + } + } } + } } -class CatsViewModelFactory(private val catsRepository: CatsRepository) : - ViewModelProvider.NewInstanceFactory() { - override fun create(modelClass: Class): T = - CatsViewModel(catsRepository) as T +class CatsViewModelFactory( + private val catsRepository: CatsRepository +) : ViewModelProvider.Factory { + 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") + } } \ No newline at end of file diff --git a/flowcats/src/main/java/otus/homework/flowcats/Fact.kt b/flowcats/src/main/java/otus/homework/flowcats/Fact.kt index 602303eb..fb5c7665 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/Fact.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/Fact.kt @@ -3,22 +3,28 @@ 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 -) \ No newline at end of file + @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 +) { + companion object { + val EMPTY_FACT = Fact( + "", false, "", "", "", false, "", "", "" + ) + } +} \ 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..1d38c439 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt @@ -1,21 +1,32 @@ package otus.homework.flowcats -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle 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() { - private val diContainer = DiContainer() - private val catsViewModel by viewModels { CatsViewModelFactory(diContainer.repository) } + 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) + 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 { + repeatOnLifecycle(Lifecycle.State.STARTED) { + catsViewModel.catsState.collect { uiState -> + when (uiState) { + is Success -> view.populate(uiState.value) + is Error -> view.showError(uiState.message) + } } + } } + } } \ 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..438959a1 --- /dev/null +++ b/flowcats/src/main/java/otus/homework/flowcats/Result.kt @@ -0,0 +1,12 @@ +package otus.homework.flowcats + + +sealed class Result + +class Success(val value: T) : Result() { + companion object { + val EMPTY = Success(Fact.EMPTY_FACT) + } +} + +class Error(val message: String) : Result() \ No newline at end of file diff --git a/operators/src/main/java/otus/homework/flow/SampleInteractor.kt b/operators/src/main/java/otus/homework/flow/SampleInteractor.kt index 1993c064..36c9c332 100644 --- a/operators/src/main/java/otus/homework/flow/SampleInteractor.kt +++ b/operators/src/main/java/otus/homework/flow/SampleInteractor.kt @@ -1,53 +1,88 @@ 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.map +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.transform +import kotlinx.coroutines.flow.zip @ExperimentalCoroutinesApi class SampleInteractor( - private val sampleRepository: SampleRepository + private val sampleRepository: SampleRepository ) { - /** - * Реализуйте функцию task1 которая последовательно: - * 1) умножает числа на 5 - * 2) убирает чила <= 20 - * 3) убирает четные числа - * 4) добавляет постфикс "won" - * 5) берет 3 первых числа - * 6) возвращает результат - */ - fun task1(): Flow { - return flowOf() - } + /** + * Реализуйте функцию task1 которая последовательно: + * 1) умножает числа на 5 + * 2) убирает чила <= 20 + * 3) убирает четные числа + * 4) добавляет постфикс "won" + * 5) берет 3 первых числа + * 6) возвращает результат + */ + fun task1(): Flow { + return sampleRepository.produceNumbers() + .map { it * 5 } + .filter { it > 20 } + .filter { it % 2 != 0 } + .map { number -> "$number won" } + .take(3) + } - /** - * Классическая задача FizzBuzz с небольшим изменением. - * Если входное число делится на 3 - эмитим само число и после него эмитим строку Fizz - * Если входное число делится на 5 - эмитим само число и после него эмитим строку Buzz - * Если входное число делится на 15 - эмитим само число и после него эмитим строку FizzBuzz - * Если число не делится на 3,5,15 - эмитим само число - */ - fun task2(): Flow { - return flowOf() - } + /** + * Классическая задача FizzBuzz с небольшим изменением. + * Если входное число делится на 3 - эмитим само число и после него эмитим строку Fizz + * Если входное число делится на 5 - эмитим само число и после него эмитим строку Buzz + * Если входное число делится на 15 - эмитим само число и после него эмитим строку FizzBuzz + * Если число не делится на 3,5,15 - эмитим само число + */ + fun task2(): Flow { + return sampleRepository.produceNumbers() + .transform { number -> + emit(number.toString()) + when { + number % 15 == 0 -> { + emit("FizzBuzz") + } + number % 3 == 0 -> { + emit("Fizz") + } + number % 5 == 0 -> { + emit("Buzz") + } + } + } + } - /** - * Реализуйте функцию task3, которая объединяет эмиты из двух flow и возвращает кортеж Pair(f1,f2), - * где f1 айтем из первого флоу, f2 айтем из второго флоу. - * Если айтемы в одно из флоу кончились то результирующий флоу также должен закончится - */ - fun task3(): Flow> { - return flowOf() + /** + * Реализуйте функцию task3, которая объединяет эмиты из двух flow и возвращает кортеж Pair(f1,f2), + * где f1 айтем из первого флоу, f2 айтем из второго флоу. + * Если айтемы в одно из флоу кончились то результирующий флоу также должен закончится + */ + fun task3(): Flow> { + return sampleRepository.produceColors().zip(sampleRepository.produceForms()) { color, form -> + Pair(color, form) } + } - /** - * Реализайте функцию task4, которая обрабатывает IllegalArgumentException и в качестве фоллбека - * эмитит число -1. - * Если тип эксепшена != IllegalArgumentException, пробросьте его дальше - * При любом исходе, будь то выброс исключения или успешная отработка функции вызовите метод dotsRepository.completed() - */ - fun task4(): Flow { - return flowOf() - } + /** + * Реализайте функцию task4, которая обрабатывает IllegalArgumentException и в качестве фоллбека + * эмитит число -1. + * Если тип эксепшена != IllegalArgumentException, пробросьте его дальше + * При любом исходе, будь то выброс исключения или успешная отработка функции вызовите метод dotsRepository.completed() + */ + fun task4(): Flow { + return sampleRepository.produceNumbers() + .catch { exception -> + if (exception is IllegalArgumentException) { + emit(-1) + } else { + throw exception + } + }.onCompletion { sampleRepository.completed() } + } } \ No newline at end of file