Skip to content
Open

done #265

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'
implementation 'androidx.activity:activity-ktx:1.9.0'
}
3 changes: 3 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatImage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package otus.homework.coroutines

data class CatImage(val url: String)
50 changes: 35 additions & 15 deletions app/src/main/java/otus/homework/coroutines/CatsPresenter.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,47 @@
package otus.homework.coroutines

import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import java.net.SocketTimeoutException
import kotlin.coroutines.cancellation.CancellationException

class CatsPresenter(
private val catsService: CatsService
private val catsService: CatsService,
private val catsServicePics: CatsServicePics
) {

private var _catsView: ICatsView? = null
val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Main + job + 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()!!)
}
scope.launch {
try {
val factDeferred = async { catsService.getCatFact() }
val imageDeferred = async { catsServicePics.getCatPicUrl() }

val fact = factDeferred.await()
val pic = imageDeferred.await()
val url = pic.firstOrNull()?.url
fact.url = url.toString()
_catsView?.populate(fact)
}

override fun onFailure(call: Call<Fact>, t: Throwable) {
CrashMonitor.trackWarning()
catch (e: Exception) {
when (e) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

тут будет перехвачено CancellationException, что сломает механизм отмены корутин, нужно прокидывать его дальше
is CancellationException -> throw e

is CancellationException -> throw e
is SocketTimeoutException -> {
_catsView?.showServerErrorToast()
}
else -> {
_catsView?.showToast(e.message.toString())
CrashMonitor.trackWarning()
}
}
}
})
}
}

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

fun detachView() {
_catsView = null
job.cancel()
}
}
8 changes: 6 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,14 @@
package otus.homework.coroutines

import retrofit2.Call
import retrofit2.http.GET

interface CatsService {

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

}
interface CatsServicePics {
@GET("v1/images/search")
suspend fun getCatPicUrl() : List<CatImage>
}
24 changes: 20 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,30 +3,46 @@ 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

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

var presenter :CatsPresenter? = null

override fun onFinishInflate() {
super.onFinishInflate()
findViewById<Button>(R.id.button).setOnClickListener {
presenter?.onInitComplete()
}
}
fun loadImageFromUrl(url: String, imageView: ImageView) {
Picasso.get()
.load(url)
.into(imageView)
}

override fun populate(fact: Fact) {
findViewById<TextView>(R.id.fact_textView).text = fact.fact
loadImageFromUrl(fact.url, findViewById<ImageView>(R.id.cat_imgView))
}

override fun showServerErrorToast() {
Toast.makeText(context, "Не удалось получить ответ от сервером", Toast.LENGTH_LONG).show()
}

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

}
}

interface ICatsView {

fun populate(fact: Fact)
fun showServerErrorToast()
fun showToast(message: String)
}
53 changes: 53 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,53 @@
package otus.homework.coroutines

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CancellationException

class CatsViewModel(
private val catsService: CatsService,
private val catsServicePics: CatsServicePics
) : ViewModel() {
private val _result = MutableLiveData<Result<Fact>>()
val result: LiveData<Result<Fact>> = _result

fun onInitComplete() {
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
CrashMonitor.trackWarning(throwable)
}
viewModelScope.launch(exceptionHandler) {
try {
val factDeferred = async { catsService.getCatFact() }
val imageDeferred = async { catsServicePics.getCatPicUrl() }

val fact = factDeferred.await()
val pic = imageDeferred.await()
val url = pic.firstOrNull()?.url
fact.url = url.toString()
_result.value = Result.Success(fact)
}
catch (e: Exception) {
when (e) {
is CancellationException -> throw e
else -> {
CrashMonitor.trackWarning(e)
_result.value = Result.Error(e)
}
}
}
}
}
override fun onCleared() {
super.onCleared()
}

sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
}
}
17 changes: 17 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatsViewModelFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package otus.homework.coroutines

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class CatsViewModelFactory(
private val catsService: CatsService,
private val catsServicePics: CatsServicePics
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(CatsViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return CatsViewModel(catsService, catsServicePics) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
7 changes: 7 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CrashMonitor.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package otus.homework.coroutines

import android.util.Log

object CrashMonitor {

/**
* Pretend this is Crashlytics/AppCenter
*/
fun trackWarning() {

}

fun trackWarning(throwable: Throwable) {
Log.d("viewModelError", throwable.toString())
}
}
7 changes: 7 additions & 0 deletions app/src/main/java/otus/homework/coroutines/DiContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ class DiContainer {
.addConverterFactory(GsonConverterFactory.create())
.build()
}
private val retrofitCatsPics by lazy {
Retrofit.Builder()
.baseUrl("https://api.thecatapi.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}

val service by lazy { retrofit.create(CatsService::class.java) }
val serviceCatsPics by lazy { retrofitCatsPics.create(CatsServicePics::class.java) }
}
4 changes: 3 additions & 1 deletion app/src/main/java/otus/homework/coroutines/Fact.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ data class Fact(
@field:SerializedName("fact")
val fact: String,
@field:SerializedName("length")
val length: Int
val length: Int,
@field:SerializedName("url")
var url: String
)
45 changes: 35 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,10 +2,18 @@ package otus.homework.coroutines

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import androidx.activity.viewModels
import java.net.SocketTimeoutException
import kotlin.coroutines.cancellation.CancellationException

class MainActivity : AppCompatActivity() {

lateinit var catsPresenter: CatsPresenter
private val viewModel: CatsViewModel by viewModels {
val catsService = diContainer.service
val catsServicePics = diContainer.serviceCatsPics
CatsViewModelFactory(catsService, catsServicePics)
}
private var _catsView: ICatsView? = null

private val diContainer = DiContainer()

Expand All @@ -14,17 +22,34 @@ class MainActivity : AppCompatActivity() {

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

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

override fun onStop() {
if (isFinishing) {
catsPresenter.detachView()
findViewById<Button>(R.id.button).setOnClickListener {
viewModel.onInitComplete()
}
viewModel.result.observe(this) { result ->
when (result) {
is CatsViewModel.Result.Success -> {
_catsView?.populate(result.data)
}
is CatsViewModel.Result.Error -> {
when (result.exception) {
is CancellationException -> throw result.exception
is SocketTimeoutException -> {
_catsView?.showServerErrorToast()
}
else -> {
_catsView?.showToast(result.exception.message.toString())
CrashMonitor.trackWarning()
}
}
}
}
}
}
override fun onStop() {
super.onStop()
}
}
17 changes: 13 additions & 4 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,33 @@
android:layout_height="match_parent"
tools:context=".MainActivity">

<ImageView
android:layout_width="200dp"
android:id="@+id/cat_imgView"
android:layout_height="200dp"
tools:src="@color/cardview_dark_background"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/fact_textView"
android:textColor="@color/black"
android:layout_marginTop="24dp"
android:textSize="24sp"
tools:text="asdfasdfasdfasdfasdfasdfasdfasdfasfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@+id/cat_imgView" />

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/more_facts"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fact_textView" />
app:layout_constraintStart_toStartOf="parent" />

</otus.homework.coroutines.CatsView>