Skip to content

ДЗ Coroutines #3

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

Open
wants to merge 4 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,10 @@ dependencies {
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.squareup.picasso:picasso:2.71828'

//coroutine retrofit
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
}
49 changes: 36 additions & 13 deletions app/src/main/java/otus/homework/coroutines/CatsPresenter.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
package otus.homework.coroutines

import retrofit2.Call
import retrofit2.Callback
import android.util.Log
import kotlinx.coroutines.*
import retrofit2.Response
import java.net.SocketTimeoutException
import kotlin.coroutines.CoroutineContext

class CatsPresenter(
private val catsService: CatsService
private val catsServiceFact: CatsService,
private val catsServiceImage: CatsService
Copy link
Collaborator

Choose a reason for hiding this comment

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

А зачем тебе два инстанса одного и того же стейтлесс класса?

Copy link
Author

Choose a reason for hiding this comment

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

для того чтобы вызывать https://aws.random.cat/ который на другом ретрофит клиенте

private val retrofitImage by lazy {
Retrofit.Builder()
.baseUrl("https://aws.random.cat/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}

) {

private var _catsView: ICatsView? = null
private val presenterScope =
PresenterScope(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 {
withContext(Dispatchers.IO) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Не совсем понял зачем тебе тогда launch с Dispatchers.Main если ты все тело выполняешь на Dispatchers.IO

try {
val factResponse = catsServiceFact.getCatFact()
val imageResponse = catsServiceImage.getCatImage()
val factImage = FactImage(factResponse, imageResponse)
_catsView?.populate(factImage)
} catch (e: Exception) {
when (e) {
is SocketTimeoutException -> {
_catsView?.showToast("Не удалось получить ответ от сервером")
}
else -> {
_catsView?.showToast(e.message.toString())
CrashMonitor.trackWarning()
e.printStackTrace()
}
}
}
}

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

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

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

class PresenterScope(
private val dispatchers: CoroutineDispatcher,
private val coroutineName: CoroutineName
) : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = dispatchers + coroutineName
}
7 changes: 6 additions & 1 deletion 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 kotlinx.coroutines.Deferred
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.GET

interface CatsService {

@GET("random?animal_type=cat")
fun getCatFact() : Call<Fact>
suspend fun getCatFact() : Fact

@GET("meow")
suspend fun getCatImage() : Image
}
26 changes: 22 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,48 @@ 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 android.widget.Toast.LENGTH_LONG
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
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
private var refreshLayout: SwipeRefreshLayout? = null


override fun onFinishInflate() {
super.onFinishInflate()
findViewById<Button>(R.id.button).setOnClickListener {
presenter?.onInitComplete()
}
refreshLayout = findViewById<SwipeRefreshLayout>(R.id.swipe)
refreshLayout?.setOnRefreshListener {
presenter?.onInitComplete()
}
}

override fun populate(factImage: FactImage) {
refreshLayout?.isRefreshing = false
findViewById<TextView>(R.id.fact_textView).text = factImage.fact.text
Picasso.get().load(factImage.image.file).into(findViewById<ImageView>(R.id.iv_image))
}

override fun populate(fact: Fact) {
findViewById<TextView>(R.id.fact_textView).text = fact.text
override fun showToast(message: String) {
Toast.makeText(context, message, LENGTH_LONG).show()
}
}

interface ICatsView {

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

import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import java.net.SocketTimeoutException

class CatsViewModel(
private val catsService: CatsService
) : ViewModel() {
private var _catsResponse = MutableLiveData<Result<FactImage>>()
val catsResponse: LiveData<Result<FactImage>>
get() = _catsResponse

fun getCatFactImage() {
viewModelScope.launch(CoroutineExceptionHandler { coroutineContext, throwable ->
CrashMonitor.trackWarning()
}) {
try {
val factResponseDeferred = async {catsService.getCatFact()}
val imageResponseDeferred = async {catsService.getCatImage()}

val factResponse = factResponseDeferred.await()
val imageResponse = imageResponseDeferred.await()

/**
* antonkazakov: ... Используй async чтобы распараллелить
* Вопрос: изначально я сделал без async чтобы они выполнялись полседовательно
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ты можешь сам это проверить на простейшем примере. Такие вещи лучше проверять самому чтобы не забывать и понимать.

Пустого параметра не будет. У тебя корутина будет сасаспендена пока не выполнится await

* чтобы два результата использовать при создании объекта
* FactImage(factResponse, imageResponse)
* теперь при использовании async как это будет работать?
* допустим получаем результат factResponse, imageResponse - неодновременно
* FactImage(factResponse, imageResponse) будет создан из того что пришло первым,
* а второй параметр "уйдет" пустым
* или
* родительская корутину (которая запускает launch)
* "дождется" всех и только потом создаст FactImage(factResponse, imageResponse) ?
*/
val factImage = FactImage(factResponse, imageResponse)
_catsResponse.value = Result.Success(factImage)

} catch (e: Exception) {
when (e) {
is SocketTimeoutException -> {
_catsResponse.value =
Result.Error("Не удалось получить ответ от сервером", e)
}
else -> {
_catsResponse.value = Result.Error(e.message.toString(), e)
e.printStackTrace()
}
}
}
}
}
}

sealed class Result<out T> {
data class Success<out R>(val value: R) : Result<R>()
data class Error(
val message: String,
val throwable: Throwable?
) : Result<Nothing>()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package otus.homework.coroutines

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

class CatsViewModelFactory(private val catsService: CatsService): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = CatsViewModel(catsService) as T
}
14 changes: 11 additions & 3 deletions app/src/main/java/otus/homework/coroutines/DiContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@ package otus.homework.coroutines
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class DiContainer {
class DiContainer() {

private val retrofit by lazy {
private val retrofitFact by lazy {
Retrofit.Builder()
.baseUrl("https://cat-fact.herokuapp.com/facts/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}

val service by lazy { retrofit.create(CatsService::class.java) }
private val retrofitImage by lazy {
Retrofit.Builder()
.baseUrl("https://aws.random.cat/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}

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

}
8 changes: 8 additions & 0 deletions app/src/main/java/otus/homework/coroutines/FactImage.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 FactImage(
val fact: Fact,
val image: Image
)
10 changes: 10 additions & 0 deletions app/src/main/java/otus/homework/coroutines/FactImageResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package otus.homework.coroutines

import com.google.gson.annotations.SerializedName

data class FactImageResponse(
val fact: Fact? = null,
val image: Image? = null,
var isSuccessful: Boolean? = true,
var errorMessage: String? = null
)
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("file")
val file: String
)
48 changes: 44 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,63 @@ package otus.homework.coroutines

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.squareup.picasso.Picasso

class MainActivity : AppCompatActivity() {

lateinit var catsPresenter: CatsPresenter

private val diContainer = DiContainer()
private lateinit var viewModel: CatsViewModel

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()
/*
### Реализовать решение ViewModel
*/
val viewModelFactory = CatsViewModelFactory(diContainer.service)
viewModel = ViewModelProvider(this, viewModelFactory).get(CatsViewModel::class.java)

viewModel.getCatFactImage()

val refreshLayout = findViewById<SwipeRefreshLayout>(R.id.swipe)
refreshLayout?.setOnRefreshListener {
viewModel.getCatFactImage()
}

viewModel.catsResponse.observe(this, Observer { factImage ->
refreshLayout.isRefreshing = false
when (factImage) {
is Result.Success -> {
findViewById<TextView>(R.id.fact_textView).text = factImage.value.fact?.text
Picasso.get().load(factImage.value.image?.file)
.into(findViewById<ImageView>(R.id.iv_image))
}
is Result.Error -> {
Toast.makeText(this, factImage.message, Toast.LENGTH_LONG).show()
}
}
})

/*
### Перейти с коллбеков на саспенд функции и корутины
### Добавить к запросу фактов запрос рандомных картинок с [https://aws.random.cat/meow](https://aws.random.cat/meow)
*/
// catsPresenter = CatsPresenter(diContainer.serviceFact, diContainer.serviceImage)
// view.presenter = catsPresenter
// catsPresenter.attachView(view)
// catsPresenter.onInitComplete()

}

override fun onStop() {
Expand Down
Loading