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
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ dependencies {
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
implementation 'androidx.room:room-runtime-android:2.8.4'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package otus.homework.coroutines

data class CatPresentationModel(
val fact: String,
val imageUrl: String
)

72 changes: 63 additions & 9 deletions app/src/main/java/otus/homework/coroutines/CatsPresenter.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,74 @@
package otus.homework.coroutines

import kotlinx.coroutines.*
import java.net.SocketTimeoutException

import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class CatsPresenter(
private val catsService: CatsService
private val catsService: CatsService,
private val catsImageService: CatsImageService
) {

private var _catsView: ICatsView? = null
private var currentJob: Job? = null

private val presenterScope = CoroutineScope(
Dispatchers.Main + SupervisorJob() + CoroutineName("CatsCoroutine")
)

fun onInitComplete() {
catsService.getCatFact().enqueue(object : Callback<Fact> {

override fun onResponse(call: Call<Fact>, response: Response<Fact>) {
if (response.isSuccessful && response.body() != null) {
_catsView?.populate(response.body()!!)
currentJob?.cancel()

currentJob = presenterScope.launch {
try {

_catsView?.showLoading(true)


val deferredFact = async(Dispatchers.IO) {
catsService.getCatFact()
}
}

override fun onFailure(call: Call<Fact>, t: Throwable) {
CrashMonitor.trackWarning()
val deferredImage = async(Dispatchers.IO) {
catsImageService.getCatImage()
}


val fact = deferredFact.await()
val image = deferredImage.await()

val enhancedFact = Fact(
fact = fact.fact,
length = fact.length,
imageUrl = image.file // Добавляем URL картинки
)

// Передаем в существующий метод populate
_catsView?.populate(enhancedFact)

_catsView?.showLoading(false)

} catch (e: CancellationException) {

_catsView?.showLoading(false)
} catch (e: SocketTimeoutException) {
_catsView?.showLoading(false)
handleError(e, "Не удалось получить ответ от сервера")
} catch (e: Exception) {
_catsView?.showLoading(false)
handleError(e, e.message ?: "Неизвестная ошибка")
}
})
}
}

private fun handleError(e: Exception, message: String) {
CrashMonitor.trackWarning()

_catsView?.showToast(message)
}

fun attachView(catsView: ICatsView) {
Expand All @@ -32,4 +78,12 @@ class CatsPresenter(
fun detachView() {
_catsView = null
}

fun onStop() {
presenterScope.coroutineContext.cancelChildren()
}

fun cancel() {
presenterScope.cancel()
}
}
13 changes: 11 additions & 2 deletions app/src/main/java/otus/homework/coroutines/CatsService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,14 @@ import retrofit2.http.GET
interface CatsService {

@GET("fact")
fun getCatFact() : Call<Fact>
}
suspend fun getCatFact() : Fact
}

interface CatsImageService {
@GET("meow")
suspend fun getCatImage(): CatImage
}

data class CatImage(
val file: String
)
22 changes: 21 additions & 1 deletion app/src/main/java/otus/homework/coroutines/CatsView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@ package otus.homework.coroutines
import android.content.Context
import android.util.AttributeSet
import android.widget.Button
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible

class CatsView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), ICatsView {

var presenter :CatsPresenter? = null
var presenter: CatsPresenter? = null

private lateinit var factTextView: TextView
private lateinit var catImageView: ImageView
private lateinit var button: Button
private lateinit var progressBar: ProgressBar

override fun onFinishInflate() {
super.onFinishInflate()
Expand All @@ -24,9 +33,20 @@ class CatsView @JvmOverloads constructor(
override fun populate(fact: Fact) {
findViewById<TextView>(R.id.fact_textView).text = fact.fact
}

override fun showToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}

override fun showLoading(isLoading: Boolean) {
progressBar.isVisible = isLoading
button.isEnabled = !isLoading
}
}

interface ICatsView {

fun populate(fact: Fact)
fun showToast(message: String)
fun showLoading(isLoading: Boolean)
}
83 changes: 83 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatsViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package otus.homework.coroutines

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import java.net.SocketTimeoutException

class CatsViewModel(
private val catsService: CatsService,
private val catsImageService: CatsImageService
) : ViewModel() {

private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->

CrashMonitor.trackWarning()


_result.value = Result.Error(
message = when (throwable) {
is SocketTimeoutException -> "Не удалось получить ответ от сервера"
else -> throwable.message ?: "Неизвестная ошибка"
},
exception = throwable
)
}

// LiveData для состояния
private val _result = MutableLiveData<Result<Fact>>()
val result: LiveData<Result<Fact>> = _result

private var currentJob: Job? = null

fun loadCatData() {

currentJob?.cancel()

currentJob = viewModelScope.launch(exceptionHandler) {
try {

_result.value = Result.Loading


val deferredFact = async { catsService.getCatFact() }
val deferredImage = async { catsImageService.getCatImage() }


val (factResult, imageResult) = awaitAll(deferredFact, deferredImage)


val factWithImage = factResult.copy(imageUrl = imageResult.file)


_result.value = Result.Success(factWithImage)

} catch (e: Exception) {

if (e is SocketTimeoutException) {
_result.value = Result.Error("Не удалось получить ответ от сервера", e)
} else {

_result.value = Result.Error(e.message ?: "Неизвестная ошибка", e)
}
}
}
}

fun cancelCurrentRequest() {
currentJob?.cancel()
currentJob = null
}

override fun onCleared() {
super.onCleared()
cancelCurrentRequest()
}
}

12 changes: 11 additions & 1 deletion app/src/main/java/otus/homework/coroutines/DiContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,15 @@ class DiContainer {
.build()
}

private val retrofitCatsImage by lazy {
Retrofit.Builder()
.baseUrl("https://aws.random.cat/") // Базовый URL для картинок
.addConverterFactory(GsonConverterFactory.create())
.build()
}

val service by lazy { retrofit.create(CatsService::class.java) }
}

val imageService by lazy { retrofitCatsImage.create(CatsImageService::class.java) }
}

10 changes: 8 additions & 2 deletions app/src/main/java/otus/homework/coroutines/Fact.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@ data class Fact(
@field:SerializedName("fact")
val fact: String,
@field:SerializedName("length")
val length: Int
)
val length: Int,

val imageUrl: String? = null
){
fun withImageUrl(imageUrl: String): Fact {
return Fact(fact, length, imageUrl)
}
}
50 changes: 40 additions & 10 deletions app/src/main/java/otus/homework/coroutines/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,59 @@ package otus.homework.coroutines

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class MainActivity : AppCompatActivity() {

lateinit var catsPresenter: CatsPresenter

private lateinit var catsViewModel: CatsViewModel
private lateinit var catsView: CatsView
private val diContainer = DiContainer()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val view = layoutInflater.inflate(R.layout.activity_main, null) as CatsView
setContentView(view)
catsView = layoutInflater.inflate(R.layout.activity_main, null) as CatsView
setContentView(catsView)


catsViewModel = ViewModelProvider(
this,

object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CatsViewModel(
diContainer.service,
diContainer.imageService
) as T
}
}
).get(CatsViewModel::class.java)


catsViewModel.result.observe(this) { result ->
when (result) {
is Result.Loading -> {
// прогресс
}
is Result.Success -> {
catsView.populate(result.data)
}
is Result.Error -> {
catsView.showToast(result.message)
}
}
}

catsPresenter = CatsPresenter(diContainer.service)
view.presenter = catsPresenter
catsPresenter.attachView(view)
catsPresenter.onInitComplete()
catsViewModel.loadCatData()
}


override fun onStop() {
super.onStop()
if (isFinishing) {
catsPresenter.detachView()
// Очистка при необходимости
}
super.onStop()
}
}
7 changes: 7 additions & 0 deletions app/src/main/java/otus/homework/coroutines/Result.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package otus.homework.coroutines

sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val message: String, val exception: Throwable? = null) : Result<Nothing>()
object Loading : Result<Nothing>()
}