Skip to content

HW Sokolova_coroutines #156

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 6 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
22 changes: 14 additions & 8 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ plugins {
}

android {
compileSdkVersion 30
compileSdkVersion 34
buildToolsVersion "30.0.3"

defaultConfig {
applicationId "otus.homework.coroutines"
minSdkVersion 23
targetSdkVersion 30
targetSdkVersion 34
versionCode 1
versionName "1.0"

Expand All @@ -34,12 +34,18 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.squareup.picasso:picasso:2.71828'
}
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
implementation 'androidx.activity:activity-ktx:1.7.2'
testImplementation 'junit:junit:4.13.2'
}
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@
</activity>
</application>

</manifest>
</manifest>
60 changes: 60 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package otus.homework.coroutines

import android.content.res.Resources
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import otus.homework.coroutines.model.CatModel
import java.net.SocketTimeoutException


class CatViewModel(
private val factService: CatsFactService,
private val imageService: CatsImageService
) : ViewModel() {
val catModel = MutableLiveData<Result>()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Мутабельные стримы: лайвдата, стейтфлоу и тп делают приватными(обычно). Это для того чтобы у тебя в стрим мог эмитить только 1 класс, а не все у кого есть ссылка на CatViewModel.

Copy link
Author

Choose a reason for hiding this comment

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

d


fun onInitComplete() {
loadData()
}

private val handler = CoroutineExceptionHandler { _, throwable ->
when (throwable) {
is SocketTimeoutException -> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Я бы это перенес в try/catch. Тут оставил только необработанные

Copy link
Author

Choose a reason for hiding this comment

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

d

catModel.value = Result.Error(Throwable(Resources.getSystem().getString(R.string.error_connection)))
}
is CancellationException -> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

в CoroutineExceptionHandler этого можно не делать, справедливо только для обычного catch

Copy link
Author

@Sokolik411 Sokolik411 Aug 14, 2023

Choose a reason for hiding this comment

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

d, унесла в try/catch

throw throwable
}
else -> {
CrashMonitor.trackWarning(throwable.message.toString())
catModel.value = Result.Error(throwable)
}
}
}

private fun loadData() {
viewModelScope.launch(handler) {
val catFactJob = async { factService.getCatFact() }
val catImageJob = async { imageService.getCatImage() }

catModel.value = Result.Success(
CatModel(
catFactJob.await().body()?.fact,
CatsImageService.BASE_URL + catImageJob.await().body()?.url
)
)

}
}

override fun onCleared() {
super.onCleared()
viewModelScope.cancel()
Copy link
Collaborator

Choose a reason for hiding this comment

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

viewModelScope сам будет очищен в onCleared

Copy link
Author

Choose a reason for hiding this comment

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

d

}
}
15 changes: 15 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatsFactService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package otus.homework.coroutines

import otus.homework.coroutines.model.Fact
import retrofit2.Response
import retrofit2.http.GET

interface CatsFactService {

@GET("fact")
suspend fun getCatFact() : Response<Fact>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Я бы просто Fact оставил

Copy link
Author

Choose a reason for hiding this comment

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

d, для картинок сделала аналогично


companion object {
const val BASE_URL = "https://catfact.ninja/"
}
}
15 changes: 15 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatsImageService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package otus.homework.coroutines

import otus.homework.coroutines.model.RandomCat
import retrofit2.Response
import retrofit2.http.GET

interface CatsImageService {

@GET("cat?json=true")
suspend fun getCatImage() : Response<RandomCat>

companion object {
const val BASE_URL = "https://cataas.com/"
}
}
56 changes: 41 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,50 @@
package otus.homework.coroutines

import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import otus.homework.coroutines.model.CatModel
import java.net.SocketTimeoutException

class CatsPresenter(
private val catsService: CatsService
private val factService: CatsFactService,
private val imageService: CatsImageService
) {

private var _catsView: ICatsView? = null
private val catsScope = PresenterScope()

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()!!)
runBlocking(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.

Зачем здесь runBlocking? Его вообще кроме как в тестах использовать нигде не нужно

Copy link
Author

Choose a reason for hiding this comment

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

d

catsScope.launch {
try {
val catFactJob = async { factService.getCatFact() }
val catImageJob = async { imageService.getCatImage() }

val catModel = CatModel(
catFactJob.await().body()?.fact,
CatsImageService.BASE_URL + catImageJob.await().body()?.url)

_catsView?.populate(catModel)
} catch (e: Exception) {
when (e) {
is SocketTimeoutException -> {
_catsView?.showToast(R.string.error_connection)
}
is CancellationException -> {
throw e
}
else -> {
CrashMonitor.trackWarning(e.message.toString())
_catsView?.showToast(e.message.toString())
}
}
}
}

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

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

fun onStop(){
catsScope.cancel()
Copy link
Collaborator

Choose a reason for hiding this comment

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

А почему это не перенести в detachView, ты же onStop вызываешь в onStop ЖЦ

Copy link
Author

Choose a reason for hiding this comment

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

d

}
}
10 changes: 0 additions & 10 deletions app/src/main/java/otus/homework/coroutines/CatsService.kt

This file was deleted.

31 changes: 25 additions & 6 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,49 @@ 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.annotation.StringRes
import androidx.constraintlayout.widget.ConstraintLayout
import com.squareup.picasso.Picasso
import otus.homework.coroutines.model.CatModel

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 :CatViewModel? = null

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

override fun populate(fact: Fact) {
findViewById<TextView>(R.id.fact_textView).text = fact.text
override fun populate(catModel: CatModel) {
findViewById<TextView>(R.id.fact_textView).text = catModel.fact
val imageView = findViewById<ImageView>(R.id.random_cat)
Picasso.get().load(catModel.imageUrl).into(imageView)
}

override fun showToast(messageRes: Int) {
Toast.makeText(context, messageRes, Toast.LENGTH_SHORT).show()
}

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

interface ICatsView {

fun populate(fact: Fact)
}
fun populate(catModel: CatModel)
fun showToast(@StringRes messageRes: Int)
fun showToast(messageRes: String)
}
10 changes: 8 additions & 2 deletions app/src/main/java/otus/homework/coroutines/CrashMonitor.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package otus.homework.coroutines

import android.util.Log

object CrashMonitor {

/**
* Pretend this is Crashlytics/AppCenter
*/
fun trackWarning() {
private val crashesList = ArrayList<String>()

fun trackWarning(message: String) {
crashesList.add(message)
Log.e("crash_mirror", message)
}
}
}
40 changes: 36 additions & 4 deletions app/src/main/java/otus/homework/coroutines/DiContainer.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,48 @@
package otus.homework.coroutines

import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

class DiContainer {

private val retrofit by lazy {
val serviceFact: CatsFactService by lazy { retrofitCatFact.create(CatsFactService::class.java) }
val serviceImage: CatsImageService by lazy { retrofitCatImage.create(CatsImageService::class.java) }

private val interceptor = run {
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.apply {
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
}
}

private val okHttpClient = OkHttpClient.Builder()
.addNetworkInterceptor(interceptor) // same for .addInterceptor(...)
.connectTimeout(30, TimeUnit.SECONDS) //Backend is really slow
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()

private val retrofitCatFact by lazy {
Retrofit.Builder()
.baseUrl("https://catfact.ninja/")
.client(okHttpClient)
.baseUrl(CatsFactService.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

val service by lazy { retrofit.create(CatsService::class.java) }
}
private val gson = GsonBuilder()
.setLenient()
.create()

private val retrofitCatImage by lazy {
Retrofit.Builder()
.client(okHttpClient)
.baseUrl(CatsImageService.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
}
24 changes: 0 additions & 24 deletions app/src/main/java/otus/homework/coroutines/Fact.kt

This file was deleted.

Loading