Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,27 @@

### Что нужно сделать:

1. **Подключить RxJava2 к Retrofit**
- Добавить `retrofit2:adapter-rxjava2` в зависимости
- Изменить `CatsService.getCatFact()` чтобы возвращал `Single<Fact>` вместо `Call<Fact>`
~~1. **Подключить RxJava2 к Retrofit**~~
- ~~Добавить `retrofit2:adapter-rxjava2` в зависимости~~
- ~~Изменить `CatsService.getCatFact()` чтобы возвращал `Single<Fact>` вместо `Call<Fact>`~~

2. **Переписать CatsViewModel на RxJava2**
- Убрать колбеки из `init` блока
- Реализовать `getFacts()` с помощью RxJava2
- Использовать `CompositeDisposable` для управления подписками, не забыть отписаться

3. **Реализовать LocalCatFactsGenerator.generateCatFact()**
- Возвращать `Single<Fact>` со случайным фактом из `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<Fact>` со случайным фактом из `R.array.local_cat_facts`~~
~~- Использовать `Single.fromCallable()` или `Single.just()`~~

~~4. **Реализовать LocalCatFactsGenerator.generateCatFactPeriodically()**~~
~~- Эмитить `Fact` каждые 2 секунды~~
~~- Пропускать дублирующиеся факты~~

~~5. **Реализовать CatsViewModel.getFacts()**~~
~~- Каждые 2 секунды запрашивать факт из сети~~
~~- При ошибке сети - использовать `LocalCatFactsGenerator.generateCatFact()` как фоллбек~~
~~- Использовать `onErrorResumeNext()` для обработки ошибок~~

### Подсказки:
- `Single` - для одноразовых операций
Expand Down
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ android {
kotlinOptions {
jvmTarget = '17'
}

buildFeatures {
viewBinding true
}
Expand All @@ -47,4 +48,5 @@ dependencies {
implementation libs.picasso
implementation libs.rxjava
implementation libs.rxandroid
implementation libs.adapter.rxjava2
}
6 changes: 3 additions & 3 deletions app/src/main/java/otus/homework/reactivecats/CatsService.kt
Original file line number Diff line number Diff line change
@@ -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<Fact>
}
fun getCatFact(): Single<Fact>
}
73 changes: 56 additions & 17 deletions app/src/main/java/otus/homework/reactivecats/CatsViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Result>()
val catsLiveData: LiveData<Result> = _catsLiveData
private val disposables = CompositeDisposable()

init {
catsService.getCatFact().enqueue(object : Callback<Fact> {
override fun onResponse(call: Call<Fact>, response: Response<Fact>) {
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<Fact>, t: Throwable) {
_catsLiveData.value = ServerError
override fun onCleared() {
super.onCleared()
disposables.clear()
}

fun getFacts(): Flowable<Fact> {
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(
Expand All @@ -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()
object ServerError : Result()
6 changes: 4 additions & 2 deletions app/src/main/java/otus/homework/reactivecats/DiContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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)
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/otus/homework/reactivecats/Fact.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ import com.google.gson.annotations.SerializedName
data class Fact(
@field:SerializedName("fact")
val fact: String
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -15,7 +16,8 @@ class LocalCatFactsGenerator(
* обернутую в подходящий стрим(Flowable/Single/Observable и т.п)
*/
fun generateCatFact(): Single<Fact> {
return Single.never()
val randomFact = Fact(context.resources.getStringArray(R.array.local_cat_facts)[Random.nextInt(5)])
return Single.just(randomFact)
}

/**
Expand All @@ -24,7 +26,8 @@ class LocalCatFactsGenerator(
* Если вновь заэмиченный Fact совпадает с предыдущим - пропускаем элемент.
*/
fun generateCatFactPeriodically(): Flowable<Fact> {
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 }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[versions]
adapter-rxjava2 = "2.9.0"
agp = "8.11.1"
kotlin = "2.2.10"
core-ktx = "1.17.0"
Expand Down Expand Up @@ -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"]
Expand Down