diff --git a/app/build.gradle b/app/build.gradle index 45c69c8..36dc61a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,13 +4,13 @@ plugins { } android { - compileSdkVersion 30 + compileSdkVersion 31 buildToolsVersion "30.0.3" defaultConfig { applicationId "otus.homework.reactivecats" minSdkVersion 23 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 1 versionName "1.0" @@ -46,4 +46,7 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' implementation "io.reactivex.rxjava2:rxjava:2.2.21" implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + + // Retrofit + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b125dfc..c1cba7c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,9 @@ 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 c79be48..17d1e38 100644 --- a/app/src/main/java/otus/homework/reactivecats/CatsService.kt +++ b/app/src/main/java/otus/homework/reactivecats/CatsService.kt @@ -1,10 +1,10 @@ package otus.homework.reactivecats -import retrofit2.Call +import io.reactivex.Single import retrofit2.http.GET interface CatsService { @GET("random?animal_type=cat") - 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 38caa5b..8dd2ce8 100644 --- a/app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt +++ b/app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt @@ -1,58 +1,73 @@ 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.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import java.util.concurrent.TimeUnit + class CatsViewModel( - catsService: CatsService, - localCatFactsGenerator: LocalCatFactsGenerator, - context: Context + private val catsService: CatsService, + private val localCatFactsGenerator: LocalCatFactsGenerator, + private val subscriptions: CompositeDisposable ) : ViewModel() { 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() + } + + private fun getFacts() { + val initialDelay = 0L + val period = 2L + val timeUnit = TimeUnit.SECONDS - override fun onFailure(call: Call, t: Throwable) { - _catsLiveData.value = ServerError + val disposable = Observable.interval(initialDelay, period, timeUnit) + .flatMap { + catsService.getCatFact().toObservable() } - }) + .onErrorResumeNext( + localCatFactsGenerator.generateCatFactPeriodically().toObservable() + ) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(::doOnSuccessReceiveFact, ::doOnError) + + subscriptions.add(disposable) } - fun getFacts() {} + override fun onCleared() { + super.onCleared() + + subscriptions.clear() + } + + private fun doOnSuccessReceiveFact(fact: Fact) { + _catsLiveData.value = Success(fact) + } + + private fun doOnError(throwable: Throwable) { + throwable.printStackTrace() + _catsLiveData.value = Error(message = throwable.localizedMessage) + } } class CatsViewModelFactory( private val catsRepository: CatsService, private val localCatFactsGenerator: LocalCatFactsGenerator, - private val context: Context + private val subscriptions: CompositeDisposable ) : ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T = - CatsViewModel(catsRepository, localCatFactsGenerator, context) as T + CatsViewModel(catsRepository, localCatFactsGenerator, subscriptions) 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 +data class Error(val message: String?) : 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 dfbb9a2..2e9cf16 100644 --- a/app/src/main/java/otus/homework/reactivecats/DiContainer.kt +++ b/app/src/main/java/otus/homework/reactivecats/DiContainer.kt @@ -1,7 +1,9 @@ package otus.homework.reactivecats import android.content.Context +import io.reactivex.disposables.CompositeDisposable import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory class DiContainer { @@ -10,10 +12,13 @@ class DiContainer { Retrofit.Builder() .baseUrl("https://cat-fact.herokuapp.com/facts/") .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) } + + val subscriptions: CompositeDisposable = CompositeDisposable() 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 4481062..f98d40b 100644 --- a/app/src/main/java/otus/homework/reactivecats/LocalCatFactsGenerator.kt +++ b/app/src/main/java/otus/homework/reactivecats/LocalCatFactsGenerator.kt @@ -3,7 +3,8 @@ package otus.homework.reactivecats import android.content.Context import io.reactivex.Flowable import io.reactivex.Single -import kotlin.random.Random +import io.reactivex.schedulers.Schedulers +import java.util.concurrent.TimeUnit class LocalCatFactsGenerator( private val context: Context @@ -15,7 +16,17 @@ class LocalCatFactsGenerator( * обернутую в подходящий стрим(Flowable/Single/Observable и т.п) */ fun generateCatFact(): Single { - return Single.never() + return Single.create { singleEmitter -> + try { + val items = context.resources.getStringArray(R.array.local_cat_facts) + val randomItem = items.random() + val factItem = Fact(text = randomItem) + + singleEmitter.onSuccess(factItem) + } catch (throwable: Throwable) { + singleEmitter.onError(throwable) + } + }.subscribeOn(Schedulers.computation()) } /** @@ -24,7 +35,12 @@ class LocalCatFactsGenerator( * Если вновь заэмиченный Fact совпадает с предыдущим - пропускаем элемент. */ fun generateCatFactPeriodically(): Flowable { - val success = Fact(context.resources.getStringArray(R.array.local_cat_facts)[Random.nextInt(5)]) - return Flowable.empty() + val repeatTime = 2L + val timeUnit = TimeUnit.SECONDS + val newFlowableCatsFact = generateCatFact().toFlowable() + + return Flowable.interval(repeatTime, timeUnit) + .flatMap { newFlowableCatsFact } + .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 8ec9571..978d2dc 100644 --- a/app/src/main/java/otus/homework/reactivecats/MainActivity.kt +++ b/app/src/main/java/otus/homework/reactivecats/MainActivity.kt @@ -1,11 +1,9 @@ package otus.homework.reactivecats -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 +import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { @@ -14,7 +12,7 @@ class MainActivity : AppCompatActivity() { CatsViewModelFactory( diContainer.service, diContainer.localCatFactsGenerator(applicationContext), - applicationContext + diContainer.subscriptions ) } @@ -25,8 +23,12 @@ class MainActivity : AppCompatActivity() { catsViewModel.catsLiveData.observe(this) { result -> when (result) { is Success -> view.populate(result.fact) - is Error -> Toast.makeText(this, result.message, Toast.LENGTH_LONG).show() - ServerError -> Snackbar.make(view, "Network error", 1000).show() + is Error -> { + val error = result.message + ?: applicationContext.getString(R.string.default_error_text) + + Toast.makeText(this, error, Toast.LENGTH_LONG).show() + } } } } diff --git a/build.gradle b/build.gradle index e782e8c..06bc6bb 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.21' repositories { google() - jcenter() + mavenCentral() } 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 @@ -17,8 +17,8 @@ buildscript { allprojects { repositories { google() + mavenCentral() // maven { url "https://oss.jfrog.org/libs-snapshot" } - jcenter() } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 77be9b4..7950783 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jun 11 16:59:36 MSK 2021 +#Wed May 04 20:13:31 MSK 2022 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-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