From 506516b2b0ea51d640e59fdbfbfae282b9c1fa01 Mon Sep 17 00:00:00 2001 From: AleksVira Date: Sat, 4 Oct 2025 22:04:13 +0300 Subject: [PATCH] =?UTF-8?q?RxJava=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B2=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82,?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=20=D0=BD=D0=B0=20Single,?= =?UTF-8?q?=20Flowable,=20=D0=B8=20=D1=82.=D0=B4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 8 ++- app/src/main/AndroidManifest.xml | 7 +- .../otus/homework/reactivecats/CatsService.kt | 6 +- .../homework/reactivecats/CatsViewModel.kt | 64 +++++++++++-------- .../otus/homework/reactivecats/DiContainer.kt | 4 +- .../reactivecats/LocalCatFactsGenerator.kt | 18 ++++-- .../homework/reactivecats/MainActivity.kt | 8 ++- app/src/main/res/values/strings.xml | 1 + .../main/res/xml/network_security_config.xml | 9 +++ gradle/libs.versions.toml | 17 ++--- 10 files changed, 93 insertions(+), 49 deletions(-) create mode 100644 app/src/main/res/xml/network_security_config.xml diff --git a/app/build.gradle b/app/build.gradle index 4a3a24af..dc286e19 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,10 @@ plugins { alias(libs.plugins.kotlinAndroid) } +kotlin { + jvmToolchain(17) +} + android { namespace 'otus.homework.reactivecats' compileSdk 36 @@ -25,9 +29,6 @@ android { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = '17' - } buildFeatures { viewBinding true } @@ -43,6 +44,7 @@ dependencies { implementation libs.fragment.ktx implementation libs.lifecycle.runtime.ktx implementation libs.bundles.network + implementation libs.retrofit.adapter.rxjava2 implementation libs.gson implementation libs.picasso implementation libs.rxjava diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a6244518..1385895e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - @@ -8,10 +7,12 @@ android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.ReactiveCats"> - diff --git a/app/src/main/java/otus/homework/reactivecats/CatsService.kt b/app/src/main/java/otus/homework/reactivecats/CatsService.kt index f30b3aac..6d40e2e9 100644 --- a/app/src/main/java/otus/homework/reactivecats/CatsService.kt +++ b/app/src/main/java/otus/homework/reactivecats/CatsService.kt @@ -1,11 +1,11 @@ package otus.homework.reactivecats -import retrofit2.Call +import io.reactivex.Single import retrofit2.http.GET interface CatsService { //@GET("random?animal_type=cat") @GET("fact") - fun getCatFact(): Call -} \ No newline at end of file + fun getCatFact(): Single +} diff --git a/app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt b/app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt index d62eaf97..cf11ad4e 100644 --- a/app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt +++ b/app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt @@ -1,58 +1,72 @@ package otus.homework.reactivecats -import android.content.Context import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response +import io.reactivex.Flowable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.schedulers.Schedulers +import java.util.concurrent.TimeUnit class CatsViewModel( - catsService: CatsService, - localCatFactsGenerator: LocalCatFactsGenerator, - context: Context + private val catsService: CatsService, + private val localCatFactsGenerator: LocalCatFactsGenerator, + private val networkErrorMessage: String, + private val defaultErrorMessage: String ) : ViewModel() { private val _catsLiveData = MutableLiveData() val catsLiveData: LiveData = _catsLiveData + private val compositeDisposable = CompositeDisposable() + init { - catsService.getCatFact().enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful && response.body() != null) { - _catsLiveData.value = Success(response.body()!!) - } else { - _catsLiveData.value = Error( - response.errorBody()?.string() ?: context.getString( - R.string.default_error_text - ) - ) - } - } + getCatFacts() + } - override fun onFailure(call: Call, t: Throwable) { - _catsLiveData.value = ServerError + fun getCatFacts() { + val disposable = Flowable.interval(2, TimeUnit.SECONDS) + .flatMapSingle { + catsService.getCatFact() + .map { Success(it) } + .onErrorResumeNext( + localCatFactsGenerator.generateCatFact().map { + SuccessWithWarning(it, networkErrorMessage) + }) } - }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + _catsLiveData.value = it + }, { + _catsLiveData.value = Error(it.message ?: defaultErrorMessage) + }) + compositeDisposable.add(disposable) } - fun getFacts() {} + override fun onCleared() { + super.onCleared() + compositeDisposable.clear() + } } class CatsViewModelFactory( private val catsRepository: CatsService, private val localCatFactsGenerator: LocalCatFactsGenerator, - private val context: Context + private val networkErrorMessage: String, + private val defaultErrorMessage: String ) : ViewModelProvider.NewInstanceFactory() { + @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T = - CatsViewModel(catsRepository, localCatFactsGenerator, context) as T + CatsViewModel(catsRepository, localCatFactsGenerator, networkErrorMessage, defaultErrorMessage) as T } sealed class Result data class Success(val fact: Fact) : Result() +data class SuccessWithWarning(val fact: Fact, val message: String) : Result() data class Error(val message: String) : Result() object ServerError : Result() \ No newline at end of file diff --git a/app/src/main/java/otus/homework/reactivecats/DiContainer.kt b/app/src/main/java/otus/homework/reactivecats/DiContainer.kt index bbccfc7b..85a4a1ea 100644 --- a/app/src/main/java/otus/homework/reactivecats/DiContainer.kt +++ b/app/src/main/java/otus/homework/reactivecats/DiContainer.kt @@ -2,6 +2,7 @@ package otus.homework.reactivecats import android.content.Context import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory class DiContainer { @@ -11,10 +12,11 @@ class DiContainer { //.baseUrl("https://cat-fact.herokuapp.com/facts/") .baseUrl("https://catfact.ninja/") .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build() } - val service by lazy { retrofit.create(CatsService::class.java) } + val service: CatsService by lazy { retrofit.create(CatsService::class.java) } fun localCatFactsGenerator(context: Context) = LocalCatFactsGenerator(context) } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/reactivecats/LocalCatFactsGenerator.kt b/app/src/main/java/otus/homework/reactivecats/LocalCatFactsGenerator.kt index 4481062e..ef2ede23 100644 --- a/app/src/main/java/otus/homework/reactivecats/LocalCatFactsGenerator.kt +++ b/app/src/main/java/otus/homework/reactivecats/LocalCatFactsGenerator.kt @@ -3,6 +3,7 @@ package otus.homework.reactivecats import android.content.Context import io.reactivex.Flowable import io.reactivex.Single +import java.util.concurrent.TimeUnit import kotlin.random.Random class LocalCatFactsGenerator( @@ -15,7 +16,11 @@ class LocalCatFactsGenerator( * обернутую в подходящий стрим(Flowable/Single/Observable и т.п) */ fun generateCatFact(): Single { - return Single.never() + return Single.fromCallable { + val facts = context.resources.getStringArray(R.array.local_cat_facts) + val randomFact = facts[Random.nextInt(facts.size)] + Fact(randomFact) + } } /** @@ -24,7 +29,12 @@ class LocalCatFactsGenerator( * Если вновь заэмиченный Fact совпадает с предыдущим - пропускаем элемент. */ fun generateCatFactPeriodically(): Flowable { - val success = Fact(context.resources.getStringArray(R.array.local_cat_facts)[Random.nextInt(5)]) - return Flowable.empty() + return Flowable.interval(2, TimeUnit.SECONDS) + .map { + val facts = context.resources.getStringArray(R.array.local_cat_facts) + val randomFact = facts[Random.nextInt(facts.size)] + Fact(randomFact) + } + .distinctUntilChanged() } -} \ No newline at end of file +} diff --git a/app/src/main/java/otus/homework/reactivecats/MainActivity.kt b/app/src/main/java/otus/homework/reactivecats/MainActivity.kt index 8ec95711..bb36930c 100644 --- a/app/src/main/java/otus/homework/reactivecats/MainActivity.kt +++ b/app/src/main/java/otus/homework/reactivecats/MainActivity.kt @@ -4,7 +4,6 @@ import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.Toast import androidx.activity.viewModels -import androidx.lifecycle.lifecycleScope import com.google.android.material.snackbar.Snackbar class MainActivity : AppCompatActivity() { @@ -14,7 +13,8 @@ class MainActivity : AppCompatActivity() { CatsViewModelFactory( diContainer.service, diContainer.localCatFactsGenerator(applicationContext), - applicationContext + getString(R.string.network_error_message), + getString(R.string.default_error_text) ) } @@ -25,6 +25,10 @@ class MainActivity : AppCompatActivity() { catsViewModel.catsLiveData.observe(this) { result -> when (result) { is Success -> view.populate(result.fact) + is SuccessWithWarning -> { + view.populate(result.fact) + Snackbar.make(view, result.message, Snackbar.LENGTH_SHORT).show() + } is Error -> Toast.makeText(this, result.message, Toast.LENGTH_LONG).show() ServerError -> Snackbar.make(view, "Network error", 1000).show() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f7024880..e692dd16 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,7 @@ Reactive Cats No cats available + Network error. Showing local fact. diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 00000000..e1eb89e5 --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 46c95785..1cbd67ed 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,18 +1,18 @@ [versions] -agp = "8.11.1" -kotlin = "2.2.10" +agp = "8.13.0" +kotlin = "2.2.20" core-ktx = "1.17.0" appcompat = "1.7.1" material = "1.13.0" constraintlayout = "2.2.1" -lifecycle-livedata-ktx = "2.9.3" -lifecycle-viewmodel-ktx = "2.9.3" +lifecycle-livedata-ktx = "2.9.4" +lifecycle-viewmodel-ktx = "2.9.4" fragment-ktx = "1.8.9" -lifecycle-runtime-ktx = "2.9.3" -gson = "2.13.1" -okhttp = "4.12.0" +lifecycle-runtime-ktx = "2.9.4" +gson = "2.13.2" +okhttp = "5.1.0" picasso = "2.71828" -retrofit = "2.9.0" +retrofit = "3.0.0" rxandroid = "2.1.1" rxjava = "2.2.21" @@ -31,6 +31,7 @@ okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-i picasso = { module = "com.squareup.picasso:picasso", version.ref = "picasso" } retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" } +retrofit-adapter-rxjava2 = { group = "com.squareup.retrofit2", name = "adapter-rxjava2", version.ref = "retrofit" } rxandroid = { module = "io.reactivex.rxjava2:rxandroid", version.ref = "rxandroid" } rxjava = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava" }