diff --git a/build.gradle b/build.gradle index e47bb55b..83efc6bb 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.4.32" + ext.kotlin_version = '1.6.20' repositories { google() jcenter() } dependencies { - classpath "com.android.tools.build:gradle:4.1.2" + classpath 'com.android.tools.build:gradle:7.0.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/flowcats/build.gradle b/flowcats/build.gradle index cefa21e0..b1fd7622 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" @@ -37,7 +37,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.3.2' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' - implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.google.code.gson:gson:2.8.7' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.android.material:material:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' @@ -46,4 +46,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.5.0-beta01" } \ No newline at end of file diff --git a/flowcats/src/main/AndroidManifest.xml b/flowcats/src/main/AndroidManifest.xml index 2deb6454..356e82b6 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 10fcb77d..9a72526e 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsRepository.kt @@ -1,18 +1,28 @@ package otus.homework.flowcats +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn class CatsRepository( private val catsService: CatsService, + private val backgroundDispatcher: CoroutineDispatcher = Dispatchers.IO, private val refreshIntervalMs: Long = 5000 ) { - fun listenForCatFacts() = flow { + fun listenForCatFacts(): Flow> = flow> { while (true) { val latestNews = catsService.getCatFact() - emit(latestNews) + emit(Success(latestNews)) delay(refreshIntervalMs) } - } + }.catch { + emit(Error) + emitAll(listenForCatFacts()) + }.flowOn(backgroundDispatcher) } \ 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..a859e9ff 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,42 @@ 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 + private val tvFact by lazy { + findViewById(R.id.fact_textView) + } + + override fun populate(result: Result) { + when (result) { + is Loading -> { + doOnLoading() + } + is Success -> { + doOnSuccess(result.data) + } + is Error -> { + doOnError() + } + } + + } + + private fun doOnLoading() { + tvFact.text = context.getString(R.string.loading_state_text) + } + + private fun doOnSuccess(fact: Fact) { + tvFact.text = fact.text + } + + private fun doOnError() { + val errorMessage = context.getString(R.string.error_state_text) + val toast = Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT) + + toast.show() } } 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..bacbc537 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/CatsViewModel.kt @@ -1,27 +1,23 @@ package otus.homework.flowcats -import androidx.lifecycle.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn class CatsViewModel( private val catsRepository: CatsRepository ) : ViewModel() { - private val _catsLiveData = MutableLiveData() - val catsLiveData: LiveData = _catsLiveData - - init { - viewModelScope.launch { - withContext(Dispatchers.IO) { - catsRepository.listenForCatFacts().collect { - _catsLiveData.value = it - } - } - } - } + val catsStateFlow: StateFlow> = catsRepository + .listenForCatFacts() + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = Loading + ) } class CatsViewModelFactory(private val catsRepository: CatsRepository) : diff --git a/flowcats/src/main/java/otus/homework/flowcats/DiContainer.kt b/flowcats/src/main/java/otus/homework/flowcats/DiContainer.kt index 485152e2..59b42d75 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/DiContainer.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/DiContainer.kt @@ -12,7 +12,7 @@ class DiContainer { .build() } - val service by lazy { retrofit.create(CatsService::class.java) } + private val service: CatsService by lazy { retrofit.create(CatsService::class.java) } val repository by lazy { CatsRepository(service) } } \ 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..789ddd13 100644 --- a/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt +++ b/flowcats/src/main/java/otus/homework/flowcats/MainActivity.kt @@ -1,21 +1,35 @@ 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 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) + initCollectingViewItems(view) + } + + private fun initCollectingViewItems(view: CatsView) { + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + catsViewModel.catsStateFlow.collect { result -> + view.populate(result) + } + } } } } \ 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..00b46cde --- /dev/null +++ b/flowcats/src/main/java/otus/homework/flowcats/Result.kt @@ -0,0 +1,6 @@ +package otus.homework.flowcats + +sealed class Result +class Success(val data: T) : Result() +object Loading : Result() +object Error : Result() \ 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..60f0286e 100644 --- a/flowcats/src/main/res/values/strings.xml +++ b/flowcats/src/main/res/values/strings.xml @@ -1,3 +1,5 @@ Flow cats + Loading… + Default error \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 88fa9252..7d31e319 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed May 05 23:18:49 MSK 2021 +#Sat Apr 23 10:36:49 MSK 2022 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/operators/src/main/java/otus/homework/flow/SampleInteractor.kt b/operators/src/main/java/otus/homework/flow/SampleInteractor.kt index b21144ef..1b60e94f 100644 --- a/operators/src/main/java/otus/homework/flow/SampleInteractor.kt +++ b/operators/src/main/java/otus/homework/flow/SampleInteractor.kt @@ -1,7 +1,14 @@ 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( @@ -10,7 +17,7 @@ class SampleInteractor( /** * Реализуйте функцию task1 которая последовательно: - * 1) возводит числа в 5ую степень + * 1) возводит числа в 5ую степень // У вас тут ошибка если делать как тут написано то тест никогда не пройдется) я так понимаю тут перемножение на 5 имелось ввиду, а не степень * 2) убирает чила <= 20 * 3) убирает четные числа * 4) добавляет постфикс "won" @@ -18,7 +25,12 @@ class SampleInteractor( * 6) возвращает результат */ fun task1(): Flow { - return flowOf() + return sampleRepository.produceNumbers() + .map { it * 5 } + .filter { it > 20 } + .filter { it % 2 != 0 } + .map { "$it won" } + .take(3) } /** @@ -29,7 +41,8 @@ class SampleInteractor( * Если число не делится на 3,5,15 - эмитим само число */ fun task2(): Flow { - return flowOf() + return sampleRepository.produceNumbers() + .fizzBuzzOperator() } /** @@ -38,7 +51,12 @@ class SampleInteractor( * Если айтемы в одно из флоу кончились то результирующий флоу также должен закончится */ fun task3(): Flow> { - return flowOf() + val colorsFlow = sampleRepository.produceColors() + val formsFlow = sampleRepository.produceForms() + + return colorsFlow.zip(formsFlow) { firstData, secondData -> + firstData to secondData + } } /** @@ -48,6 +66,38 @@ class SampleInteractor( * При любом исходе, будь то выброс исключения или успешная отработка функции вызовите метод dotsRepository.completed() */ fun task4(): Flow { - return flowOf() + val defaultValue = -1 + + return sampleRepository.produceNumbers() + .catch { exception -> + if (exception is IllegalArgumentException) { + emit(defaultValue) + } else { + throw exception + } + } + .onCompletion { + sampleRepository.completed() + } + } + + private fun Flow.fizzBuzzOperator(): Flow = transform { value -> + val fizz = "Fizz" + val buzz = "Buzz" + val fizzBuzz = "$fizz$buzz" + + emit(value.toString()) + + when { + value % 15 == 0 -> { + emit(fizzBuzz) + } + value % 3 == 0 -> { + emit(fizz) + } + value % 5 == 0 -> { + emit(buzz) + } + } } } \ No newline at end of file diff --git a/operators/src/test/java/otus/homework/flow/SampleInteractorTest.kt b/operators/src/test/java/otus/homework/flow/SampleInteractorTest.kt index 97f4d4db..1e51bad1 100644 --- a/operators/src/test/java/otus/homework/flow/SampleInteractorTest.kt +++ b/operators/src/test/java/otus/homework/flow/SampleInteractorTest.kt @@ -125,7 +125,6 @@ class SampleInteractorTest { runBlockingTest { dotsInteractor.task4().toList() } - } verify(exactly = 1) { dotsRepository.completed() } }