diff --git a/README.md b/README.md index 3cc1abd..7373024 100644 --- a/README.md +++ b/README.md @@ -4,27 +4,27 @@ ### Что нужно сделать: -1. **Подключить RxJava2 к Retrofit** - - Добавить `retrofit2:adapter-rxjava2` в зависимости - - Изменить `CatsService.getCatFact()` чтобы возвращал `Single` вместо `Call` +~~1. **Подключить RxJava2 к Retrofit**~~ + - ~~Добавить `retrofit2:adapter-rxjava2` в зависимости~~ + - ~~Изменить `CatsService.getCatFact()` чтобы возвращал `Single` вместо `Call`~~ 2. **Переписать CatsViewModel на RxJava2** - - Убрать колбеки из `init` блока - - Реализовать `getFacts()` с помощью RxJava2 - - Использовать `CompositeDisposable` для управления подписками, не забыть отписаться - -3. **Реализовать LocalCatFactsGenerator.generateCatFact()** - - Возвращать `Single` со случайным фактом из `R.array.local_cat_facts` - - Использовать `Single.fromCallable()` или `Single.just()` - -4. **Реализовать LocalCatFactsGenerator.generateCatFactPeriodically()** - - Эмитить `Fact` каждые 2 секунды - - Пропускать дублирующиеся факты - -5. **Реализовать CatsViewModel.getFacts()** - - Каждые 2 секунды запрашивать факт из сети - - При ошибке сети - использовать `LocalCatFactsGenerator.generateCatFact()` как фоллбек - - Использовать `onErrorResumeNext()` для обработки ошибок + ~~- Убрать колбеки из `init` блока~~ + ~~- Реализовать `getFacts()` с помощью RxJava2~~ + ~~- Использовать `CompositeDisposable` для управления подписками, не забыть отписаться~~ + +~~3. **Реализовать LocalCatFactsGenerator.generateCatFact()**~~ + ~~- Возвращать `Single` со случайным фактом из `R.array.local_cat_facts`~~ + ~~- Использовать `Single.fromCallable()` или `Single.just()`~~ + +~~4. **Реализовать LocalCatFactsGenerator.generateCatFactPeriodically()**~~ + ~~- Эмитить `Fact` каждые 2 секунды~~ + ~~- Пропускать дублирующиеся факты~~ + +~~5. **Реализовать CatsViewModel.getFacts()**~~ + ~~- Каждые 2 секунды запрашивать факт из сети~~ + ~~- При ошибке сети - использовать `LocalCatFactsGenerator.generateCatFact()` как фоллбек~~ + ~~- Использовать `onErrorResumeNext()` для обработки ошибок~~ ### Подсказки: - `Single` - для одноразовых операций diff --git a/app/build.gradle b/app/build.gradle index 4a3a24a..8b93761 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,6 +28,7 @@ android { kotlinOptions { jvmTarget = '17' } + buildFeatures { viewBinding true } @@ -47,4 +48,5 @@ dependencies { implementation libs.picasso implementation libs.rxjava implementation libs.rxandroid + implementation libs.adapter.rxjava2 } diff --git a/app/src/main/java/otus/homework/reactivecats/CatsService.kt b/app/src/main/java/otus/homework/reactivecats/CatsService.kt index f30b3aa..6d40e2e 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 d62eaf9..2910b0c 100644 --- a/app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt +++ b/app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt @@ -5,40 +5,79 @@ 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.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import retrofit2.HttpException +import java.net.ConnectException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import java.util.concurrent.TimeUnit +import javax.net.ssl.SSLHandshakeException + class CatsViewModel( - catsService: CatsService, - localCatFactsGenerator: LocalCatFactsGenerator, + private val catsService: CatsService, + private val localCatFactsGenerator: LocalCatFactsGenerator, context: Context ) : ViewModel() { private val _catsLiveData = MutableLiveData() val catsLiveData: LiveData = _catsLiveData + private val disposables = 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 { + val disposable: Disposable = getFacts() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { res -> + _catsLiveData.value = Success(res) + }, { error -> _catsLiveData.value = Error( - response.errorBody()?.string() ?: context.getString( + error.message ?: context.getString( R.string.default_error_text ) ) } - } + ) + disposables.add(disposable) + } - override fun onFailure(call: Call, t: Throwable) { - _catsLiveData.value = ServerError + override fun onCleared() { + super.onCleared() + disposables.clear() + } + + fun getFacts(): Flowable { + return Flowable.interval(0L, 2000L, TimeUnit.MILLISECONDS) + .flatMapSingle { + catsService.getCatFact() + .onErrorResumeNext { error -> + // Добавил проверку ошибки "сетевая ли?" согласно пункту 5.2. Возможно, я не так понял. + if (error.isNetworkException()) { + localCatFactsGenerator.generateCatFact() + } else { + Single.just(Fact("")) + } + } } - }) + .distinctUntilChanged { prev, cur -> prev.fact == cur.fact } } - fun getFacts() {} + private fun Throwable.isNetworkException(): Boolean { + val networkErrors = listOf( + UnknownHostException::class, + SocketTimeoutException::class, + SSLHandshakeException::class, + ConnectException::class, + HttpException::class + ) + return networkErrors.contains(this::class) + } } class CatsViewModelFactory( @@ -55,4 +94,4 @@ class CatsViewModelFactory( sealed class Result data class Success(val fact: Fact) : Result() data class Error(val message: String) : Result() -object ServerError : Result() \ No newline at end of file +object ServerError : Result() diff --git a/app/src/main/java/otus/homework/reactivecats/DiContainer.kt b/app/src/main/java/otus/homework/reactivecats/DiContainer.kt index bbccfc7..f9e8585 100644 --- a/app/src/main/java/otus/homework/reactivecats/DiContainer.kt +++ b/app/src/main/java/otus/homework/reactivecats/DiContainer.kt @@ -3,6 +3,7 @@ package otus.homework.reactivecats import android.content.Context import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 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/Fact.kt b/app/src/main/java/otus/homework/reactivecats/Fact.kt index c0d0256..6ed26e2 100644 --- a/app/src/main/java/otus/homework/reactivecats/Fact.kt +++ b/app/src/main/java/otus/homework/reactivecats/Fact.kt @@ -5,4 +5,4 @@ import com.google.gson.annotations.SerializedName data class Fact( @field:SerializedName("fact") val fact: String -) \ 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 4481062..b70987e 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,8 @@ class LocalCatFactsGenerator( * обернутую в подходящий стрим(Flowable/Single/Observable и т.п) */ fun generateCatFact(): Single { - return Single.never() + val randomFact = Fact(context.resources.getStringArray(R.array.local_cat_facts)[Random.nextInt(5)]) + return Single.just(randomFact) } /** @@ -24,7 +26,8 @@ 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(0, 2, TimeUnit.SECONDS) + .map { Fact(context.resources.getStringArray(R.array.local_cat_facts)[Random.nextInt(5)]) } + .distinctUntilChanged { prev, next -> prev.fact == next.fact } } -} \ 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 8ec9571..b5d72a5 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() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 46c9578..56b15d0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,5 @@ [versions] +adapter-rxjava2 = "2.9.0" agp = "8.11.1" kotlin = "2.2.10" core-ktx = "1.17.0" @@ -33,6 +34,7 @@ retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" } rxandroid = { module = "io.reactivex.rxjava2:rxandroid", version.ref = "rxandroid" } rxjava = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava" } +adapter-rxjava2 = { module = "com.squareup.retrofit2:adapter-rxjava2", version.ref = "adapter-rxjava2" } [bundles] network = ["okhttp", "okhttp-logging-interceptor", "retrofit", "retrofit-converter-gson"]