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
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,7 @@ 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.8.7'
implementation "androidx.activity:activity-ktx:1.9.3"
}
52 changes: 40 additions & 12 deletions app/src/main/java/otus/homework/coroutines/CatsPresenter.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
package otus.homework.coroutines

import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import com.google.gson.annotations.SerializedName
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import java.net.SocketTimeoutException

class CatsPresenter(
private val catsService: CatsService
private val catsService: CatsService,
private val imageCatsService: ImageCatsService
) {

private var _catsView: ICatsView? = null
private val presenterScope = CoroutineScope(SupervisorJob() + Dispatchers.Main + 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()!!)
presenterScope.launch {
try {
coroutineScope {
val fact = async { catsService.getCatFact() }
val image = async { imageCatsService.getImageCatFact().first() }
val cat = Cat(fact.await(), image.await())
_catsView?.populate(cat)
}
}

override fun onFailure(call: Call<Fact>, t: Throwable) {
catch (e: SocketTimeoutException) {
_catsView?.showToast("Не удалось получить ответ от сервера")
}
catch (e: Exception) {
CrashMonitor.trackWarning()
e.message?.let { _catsView?.showToast(it) }
}
})
}
}

fun attachView(catsView: ICatsView) {
Expand All @@ -31,5 +46,18 @@ class CatsPresenter(

fun detachView() {
_catsView = null
presenterScope.cancel()
}
}
}

data class Cat(
val fact: Fact,
val image: Image
)

data class Image(
@field:SerializedName("id")
val id: String,
@field:SerializedName("url")
val url: String
)
9 changes: 7 additions & 2 deletions app/src/main/java/otus/homework/coroutines/CatsService.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package otus.homework.coroutines

import retrofit2.Call
import retrofit2.http.GET

interface CatsService {

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

interface ImageCatsService {

@GET("v1/images/search")
suspend fun getImageCatFact() : List<Image>
}
28 changes: 24 additions & 4 deletions app/src/main/java/otus/homework/coroutines/CatsView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ package otus.homework.coroutines
import android.content.Context
import android.util.AttributeSet
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout
import com.squareup.picasso.Picasso
import kotlinx.coroutines.Deferred

class CatsView @JvmOverloads constructor(
context: Context,
Expand All @@ -13,20 +17,36 @@ class CatsView @JvmOverloads constructor(
) : ConstraintLayout(context, attrs, defStyleAttr), ICatsView {

var presenter :CatsPresenter? = null
var viewModel: CatsViewModel? = null

private var catImageView: ImageView? = null

override fun onFinishInflate() {
super.onFinishInflate()
catImageView = findViewById(R.id.image)
findViewById<Button>(R.id.button).setOnClickListener {
presenter?.onInitComplete()
//presenter?.onInitComplete()
viewModel?.getCat()
}
}

override fun populate(fact: Fact) {
findViewById<TextView>(R.id.fact_textView).text = fact.fact
override fun populate(cat: Cat) {
findViewById<TextView>(R.id.fact_textView).text = cat.fact.fact
Picasso.get().load(cat.image.url).into(findViewById<ImageView>(R.id.image))
}

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

override fun getCatImageView(): ImageView {
return catImageView ?: throw IllegalStateException("ImageView not initialized")
}
}

interface ICatsView {

fun populate(fact: Fact)
fun populate(cat: Cat)
fun showToast(message: String)
fun getCatImageView(): ImageView
}
63 changes: 63 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,63 @@
package otus.homework.coroutines

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import java.net.SocketTimeoutException
import kotlin.coroutines.cancellation.CancellationException

class CatsViewModel(
private val catsService: CatsService,
private val imageCatsService: ImageCatsService
) : ViewModel() {

private val _catsLiveData = MutableLiveData<Result>()
val catsLiveData: LiveData<Result> = _catsLiveData

private val coroutineExceptionHandler = CoroutineExceptionHandler { _, t ->
when (t) {
is CancellationException -> throw t
is SocketTimeoutException -> _catsLiveData.value =
Error("Не удалось получить ответ от сервера")

else -> {
_catsLiveData.value = Error(t.message ?: "Неизвестная ошибка")
CrashMonitor.trackWarning()
}
}
}

init {
getCat()
}

fun getCat() {
viewModelScope.launch(coroutineExceptionHandler) {
coroutineScope {
val fact = async { catsService.getCatFact() }
val image = async { imageCatsService.getImageCatFact().first() }
_catsLiveData.value = Success(Cat(fact.await(), image.await()))
}
}
}
}

class CatsViewModelFactory(
private val catsService: CatsService,
private val imageService: ImageCatsService
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T = modelClass.getConstructor(
CatsService::class.java,
ImageCatsService::class.java
).newInstance(catsService, imageService)
}

sealed class Result
data class Success(val cat: Cat) : Result()
data class Error(val message: String) : Result()
13 changes: 9 additions & 4 deletions app/src/main/java/otus/homework/coroutines/DiContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import retrofit2.converter.gson.GsonConverterFactory

class DiContainer {

private val retrofit by lazy {
private fun retrofit(url: String) : Retrofit =
Retrofit.Builder()
.baseUrl("https://catfact.ninja/")
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

val service by lazy { retrofit.create(CatsService::class.java) }
val catsService: CatsService by lazy { retrofit(FACT).create(CatsService::class.java) }
val imageCatsService: ImageCatsService by lazy { retrofit(IMAGE).create(ImageCatsService::class.java) }

companion object {
private const val FACT = "https://catfact.ninja/"
private const val IMAGE = "https://api.thecatapi.com/"
}
}
27 changes: 23 additions & 4 deletions app/src/main/java/otus/homework/coroutines/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,42 @@ package otus.homework.coroutines

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels

class MainActivity : AppCompatActivity() {

lateinit var catsPresenter: CatsPresenter

private val diContainer = DiContainer()

private val catsViewModel by viewModels<CatsViewModel> {
CatsViewModelFactory(
diContainer.catsService,
diContainer.imageCatsService
)
}

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

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

catsPresenter = CatsPresenter(diContainer.service)
view.presenter = catsPresenter
catsPresenter.attachView(view)
catsPresenter.onInitComplete()
// Presenter
// catsPresenter = CatsPresenter(diContainer.catsService, diContainer.imageCatsService)
// view.presenter = catsPresenter
// catsPresenter.attachView(view)
// catsPresenter.onInitComplete()

// ViewModel
view.viewModel = catsViewModel
catsViewModel.catsLiveData.observe(this) { result ->
when (result) {
is Success -> view.populate(result.cat)
is Error -> view.showToast(result.message)
}
}

}

override fun onStop() {
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
android:layout_height="match_parent"
tools:context=".MainActivity">

<ImageView
android:id="@+id/image"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="fitCenter"
app:layout_constraintBottom_toTopOf="@+id/fact_textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/fact_textView"
android:textColor="@color/black"
Expand Down