diff --git a/app/build.gradle b/app/build.gradle index 4a3a24a..31a5c73 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,6 +45,15 @@ dependencies { implementation libs.bundles.network implementation libs.gson implementation libs.picasso - implementation libs.rxjava +// implementation libs.rxjava implementation libs.rxandroid + + // Retrofit + RxJava2 Adapter + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0' + + // RxJava2 + implementation 'io.reactivex.rxjava2:rxjava:2.2.21' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' } diff --git a/app/src/main/java/otus/homework/reactivecats/CatsService.kt b/app/src/main/java/otus/homework/reactivecats/CatsService.kt index f30b3aa..cf69501 100644 --- a/app/src/main/java/otus/homework/reactivecats/CatsService.kt +++ b/app/src/main/java/otus/homework/reactivecats/CatsService.kt @@ -1,11 +1,12 @@ 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 + fun getCatFact(): Single } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt b/app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt index d62eaf9..62e4bcd 100644 --- a/app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt +++ b/app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt @@ -5,40 +5,111 @@ 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.Single + +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 context: Context ) : ViewModel() { +// private val _catsLiveData = MutableLiveData() +// val catsLiveData: LiveData = _catsLiveData + + private val compositeDisposable = CompositeDisposable() private val _catsLiveData = MutableLiveData() val catsLiveData: LiveData = _catsLiveData + 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 - ) + + getFacts() + +// 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 +// ) +// ) +// } +// } +// +// override fun onFailure(call: Call, t: Throwable) { +// _catsLiveData.value = ServerError +// } +// }) + } + + fun getFacts() { + // Очищаем предыдущие подписки перед новым запросом + compositeDisposable.clear() + + val disposable = io.reactivex.Observable.interval(0, 2, TimeUnit.SECONDS) + .flatMapSingle { _ -> + catsService.getCatFact() + .onErrorResumeNext { throwable: Throwable -> + if (throwable is java.io.IOException) { + localCatFactsGenerator.generateCatFact() + } else { + io.reactivex.Single.error(throwable) + } + } + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { fact -> + _catsLiveData.value = Result.Success(Single.just(fact)) + }, + { error -> + _catsLiveData.value = Result.Error(error.message ?: "Error") + } + ) + + compositeDisposable.add(disposable) + } + + private fun handleError(throwable: Throwable) { + when { + throwable is java.net.ConnectException || + throwable is java.net.SocketTimeoutException || + throwable is java.net.UnknownHostException -> { + // Ошибка сети - используем локальный генератор + try { + val localFact = localCatFactsGenerator.generateCatFact() + _catsLiveData.value = Result.Success(localFact) + } catch (e: Exception) { + _catsLiveData.value = Result.Error( + context.getString(R.string.default_error_text) ) } } - - override fun onFailure(call: Call, t: Throwable) { - _catsLiveData.value = ServerError + throwable is retrofit2.HttpException -> { + // HTTP ошибка + _catsLiveData.value = Result.Error( + throwable.message ?: context.getString(R.string.default_error_text) + ) } - }) + else -> { + _catsLiveData.value = Result.ServerError + } + } } - fun getFacts() {} + override fun onCleared() { + super.onCleared() + compositeDisposable.clear() + } } class CatsViewModelFactory( @@ -52,7 +123,9 @@ class CatsViewModelFactory( CatsViewModel(catsRepository, localCatFactsGenerator, context) as T } -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 +sealed class Result { + data class Success(val fact: Single) : Result() + data class Error(val message: String) : Result() + data class Loading(val isLoading: Boolean) : Result() // Добавляем состояние загрузки + object ServerError : Result() +} \ 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..f2606c9 100644 --- a/app/src/main/java/otus/homework/reactivecats/LocalCatFactsGenerator.kt +++ b/app/src/main/java/otus/homework/reactivecats/LocalCatFactsGenerator.kt @@ -3,6 +3,8 @@ package otus.homework.reactivecats import android.content.Context import io.reactivex.Flowable import io.reactivex.Single +import io.reactivex.schedulers.Schedulers +import java.util.concurrent.TimeUnit import kotlin.random.Random class LocalCatFactsGenerator( @@ -15,7 +17,24 @@ class LocalCatFactsGenerator( * обернутую в подходящий стрим(Flowable/Single/Observable и т.п) */ fun generateCatFact(): Single { - return Single.never() + return Single.fromCallable { + // Получаем массив строк из ресурсов + val factsArray = context.resources.getStringArray(R.array.local_cat_facts) + + // Проверяем, что массив не пустой + if (factsArray.isEmpty()) { + throw IllegalStateException("Local cat facts array is empty") + } + + // Выбираем случайную строку + val randomIndex = Random.nextInt(factsArray.size) + val randomFactText = factsArray[randomIndex] + + // Создаем объект Fact + Fact( + fact = randomFactText + ) + } } /** @@ -23,8 +42,19 @@ class LocalCatFactsGenerator( * чтобы она эмитила Fact со случайной строкой из массива строк R.array.local_cat_facts каждые 2000 миллисекунд. * Если вновь заэмиченный Fact совпадает с предыдущим - пропускаем элемент. */ +// fun generateCatFactPeriodically(): Flowable { +// val success = Fact(context.resources.getStringArray(R.array.local_cat_facts)[Random.nextInt(5)]) +// return Flowable.empty() +// } + fun generateCatFactPeriodically(): Flowable { - val success = Fact(context.resources.getStringArray(R.array.local_cat_facts)[Random.nextInt(5)]) - return Flowable.empty() + return Flowable.interval(2000, TimeUnit.MILLISECONDS) + .subscribeOn(Schedulers.io()) + .map { + val array = context.resources.getStringArray(R.array.local_cat_facts) + val text = array[Random.nextInt(array.size)] + Fact(text) + } + .distinctUntilChanged { old, new -> old.fact == new.fact } } } \ No newline at end of file