Skip to content

Coroutines Homework by Diana #253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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.6.2"
implementation("androidx.activity:activity-ktx:1.8.0")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package otus.homework.coroutines

import retrofit2.http.GET

interface CatsImageService {

@GET("v1/images/search")
suspend fun getCatImage(): List<Image>
}
54 changes: 39 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,48 @@
package otus.homework.coroutines

import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import android.content.Context
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import java.net.SocketTimeoutException

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

private var _catsView: ICatsView? = null
private val presenterScope =
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()!!)
presenterScope.coroutineContext[Job]?.cancel()
Copy link

Choose a reason for hiding this comment

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

Так отменять неправильно. Так мы отменяем родительскую джобу. А надо дочернюю.

Правильно было бы написать что-то типа такого:

private var loadJob: Job? = null

fun onInitComplete() {
  loadJob?.cancel()
  loadJob = presenterScope.launch {
    …
  }
}

presenterScope.launch {
Copy link

Choose a reason for hiding this comment

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

Вот здесь создается дочерняя джоба. Ее и надо трекать.

try {
supervisorScope {
val factDeferred = async { catsService.getCatFact() }
val imageDeferred = async { catsImageService.getCatImage() }
val factResponse = factDeferred.await()
val imageResponse = imageDeferred.await()

_catsView?.populate(
PresentationFact(factResponse.fact, imageResponse.first().imageUrl)
)
}
} catch (e: Exception) {
if (e is SocketTimeoutException) {
_catsView?.showToast(context.getString(R.string.server_error))
} else {
_catsView?.showToast(e.message ?: context.getString(R.string.error))
CrashMonitor.trackWarning()
}
}

override fun onFailure(call: Call<Fact>, t: Throwable) {
CrashMonitor.trackWarning()
}
})
}
}

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

fun cancel() {
presenterScope.cancel()
}
}
3 changes: 1 addition & 2 deletions app/src/main/java/otus/homework/coroutines/CatsService.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package otus.homework.coroutines

import retrofit2.Call
import retrofit2.http.GET

interface CatsService {

@GET("fact")
fun getCatFact() : Call<Fact>
suspend fun getCatFact(): Fact
}
22 changes: 17 additions & 5 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,42 @@ 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
// var presenter: CatsPresenter? = null
var viewModel: CatsViewModel? = null

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

override fun populate(fact: Fact) {
override fun populate(fact: PresentationFact) {
findViewById<TextView>(R.id.fact_textView).text = fact.fact
Picasso
.get()
.load(fact.imageUrl)
.into(findViewById<ImageView>(R.id.fact_image))
}

override fun showToast(text: String) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
}
}

interface ICatsView {

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

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class CatsViewModel(
private val catsService: CatsService,
private val catsImageService: CatsImageService
) : ViewModel() {
val handler = CoroutineExceptionHandler { _, exception ->
_factState.value = Result.Error(exception)
CrashMonitor.trackWarning()
}
private val _factState = MutableStateFlow<Result>(Result.Loading)
val factState: StateFlow<Result> = _factState

fun onInitComplete() {
_factState.value = Result.Loading
viewModelScope.launch(handler) {
val factDeferred = async { catsService.getCatFact() }
val imageDeferred = async { catsImageService.getCatImage() }
val factResponse = factDeferred.await()
val imageResponse = imageDeferred.await()

_factState.value = Result.Success(
PresentationFact(factResponse.fact, imageResponse.first().imageUrl)
)
}
}
}

sealed class Result() {
data class Success(val data: PresentationFact) : Result()
data class Error(val exception: Throwable) : Result()
object Loading : Result()
}
16 changes: 16 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,16 @@
package otus.homework.coroutines

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

class CatsViewModelFactory(
private val catsService: CatsService,
private val catImagesService: CatsImageService
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(CatsViewModel::class.java)) {
return CatsViewModel(catsService, catImagesService) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
12 changes: 9 additions & 3 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,18 @@ import retrofit2.converter.gson.GsonConverterFactory

class DiContainer {

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

val service by lazy { retrofit.create(CatsService::class.java) }
private val catsImageRetrofit by lazy {
Retrofit.Builder()
.baseUrl("https://api.thecatapi.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val catsImageservice by lazy { catsImageRetrofit.create(CatsImageService::class.java) }
val catsService by lazy { catsRetrofit.create(CatsService::class.java) }
}
8 changes: 8 additions & 0 deletions app/src/main/java/otus/homework/coroutines/Image.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package otus.homework.coroutines

import com.google.gson.annotations.SerializedName

data class Image(
@field:SerializedName("url")
val imageUrl: String?
)
44 changes: 31 additions & 13 deletions app/src/main/java/otus/homework/coroutines/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,30 +1,48 @@
package otus.homework.coroutines

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import java.net.SocketTimeoutException

class MainActivity : AppCompatActivity() {

lateinit var catsPresenter: CatsPresenter

private val diContainer = DiContainer()

private val viewModel: CatsViewModel by viewModels {
CatsViewModelFactory(
catsService = diContainer.catsService,
catImagesService = diContainer.catsImageservice
)
}

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()
}

override fun onStop() {
if (isFinishing) {
catsPresenter.detachView()
view.viewModel = viewModel
viewModel.onInitComplete()

lifecycleScope.launch {
viewModel.factState.collect { result ->
when (result) {
is Result.Success -> view.populate(result.data)

is Result.Error -> {
if (result.exception is SocketTimeoutException) {
view.showToast(getString(R.string.server_error))
} else {
view.showToast(result.exception.message ?: getString(R.string.error))
}
}

is Result.Loading -> {}
}
}
}
super.onStop()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package otus.homework.coroutines

data class PresentationFact(
val fact: String?,
val imageUrl: String?
)
16 changes: 13 additions & 3 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:padding="16dp"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity">

<ImageView
android:id="@+id/fact_image"
android:layout_width="wrap_content"
android:layout_height="200dp"
android:contentDescription=""
android:scaleType="fitCenter"
app:layout_constraintBottom_toTopOf="@id/fact_textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

<TextView
android:id="@+id/fact_textView"
android:textColor="@color/black"
android:textSize="24sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<resources>
<string name="app_name">Cat Facts </string>
<string name="more_facts">More Facts</string>
<string name="server_error">Не удалось получить ответ от сервера</string>
<string name="error">Что-то пошло не так</string>
<string name="cat_image">Фото кота</string>
</resources>