From 64224dc8fe8c652df35e98b87869034449629883 Mon Sep 17 00:00:00 2001 From: Konovalov Kirill Date: Tue, 16 Apr 2024 16:39:04 +0300 Subject: [PATCH] Homework --- app/build.gradle | 7 +- .../otus/homework/coroutines/CatsPresenter.kt | 47 +++++++++----- .../otus/homework/coroutines/CatsService.kt | 5 +- .../java/otus/homework/coroutines/CatsView.kt | 16 +++-- .../otus/homework/coroutines/CatsViewModel.kt | 65 +++++++++++++++++++ .../otus/homework/coroutines/DiContainer.kt | 11 +++- .../java/otus/homework/coroutines/ImageCat.kt | 14 ++++ .../homework/coroutines/ImageCatService.kt | 10 +++ .../otus/homework/coroutines/MainActivity.kt | 35 ++++++---- .../homework/coroutines/ModelPresentation.kt | 6 ++ app/src/main/res/layout/activity_main.xml | 10 ++- build.gradle | 4 +- 12 files changed, 187 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/otus/homework/coroutines/CatsViewModel.kt create mode 100644 app/src/main/java/otus/homework/coroutines/ImageCat.kt create mode 100644 app/src/main/java/otus/homework/coroutines/ImageCatService.kt create mode 100644 app/src/main/java/otus/homework/coroutines/ModelPresentation.kt diff --git a/app/build.gradle b/app/build.gradle index a414e0e8..e1bfe6ec 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,9 +36,12 @@ dependencies { implementation 'androidx.core:core-ktx:1.12.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' - implementation 'com.google.code.gson:gson:2.10' + implementation 'com.google.code.gson:gson:2.10.1' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.11.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'com.squareup.picasso:picasso:2.71828' -} \ No newline at end of file + implementation "com.squareup.okhttp3:logging-interceptor:4.11.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0" + implementation 'androidx.activity:activity-ktx:1.8.2' +} diff --git a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt b/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt index e4b05120..569073e7 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt @@ -1,29 +1,43 @@ package otus.homework.coroutines -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import java.net.SocketTimeoutException class CatsPresenter( - private val catsService: CatsService + private val catsService: CatsService, + private val imageCatService: ImageCatService ) { private var _catsView: ICatsView? = null + private var error = false - fun onInitComplete() { - catsService.getCatFact().enqueue(object : Callback { - - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful && response.body() != null) { - _catsView?.populate(response.body()!!) - } - } + private val presenterScope = CoroutineScope(Dispatchers.Main.immediate) - override fun onFailure(call: Call, t: Throwable) { + fun onInitComplete() { + val fact = presenterScope.async { catsService.getCatFact() } + val image = presenterScope.async { imageCatService.getCatImage() } + presenterScope.launch { + try { + val responseFact = fact.await() + val responseImage = image.await() + if (responseFact.isSuccessful && responseFact.body() != null + && responseImage.isSuccessful && responseImage.body() != null) { + error = false + _catsView?.populate(ModelPresentation(responseFact.body()!!, responseImage.body()!!.first())) + } else {error = true} + } catch (s: SocketTimeoutException) { + error = true + } catch (e: Exception) { + error = true CrashMonitor.trackWarning() } - }) - } + } + + } fun attachView(catsView: ICatsView) { _catsView = catsView @@ -31,5 +45,6 @@ class CatsPresenter( fun detachView() { _catsView = null + presenterScope.cancel() } -} \ No newline at end of file +} diff --git a/app/src/main/java/otus/homework/coroutines/CatsService.kt b/app/src/main/java/otus/homework/coroutines/CatsService.kt index 479b2cfb..ef6f0fd7 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsService.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsService.kt @@ -1,10 +1,11 @@ package otus.homework.coroutines import retrofit2.Call +import retrofit2.Response import retrofit2.http.GET interface CatsService { @GET("fact") - fun getCatFact() : Call -} \ No newline at end of file + suspend fun getCatFact() : Response +} diff --git a/app/src/main/java/otus/homework/coroutines/CatsView.kt b/app/src/main/java/otus/homework/coroutines/CatsView.kt index be04b2a8..e07b8e2b 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsView.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsView.kt @@ -3,8 +3,10 @@ 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 androidx.constraintlayout.widget.ConstraintLayout +import com.squareup.picasso.Picasso class CatsView @JvmOverloads constructor( context: Context, @@ -12,7 +14,7 @@ class CatsView @JvmOverloads constructor( defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr), ICatsView { - var presenter :CatsPresenter? = null + var presenter :CatsViewModel? = null override fun onFinishInflate() { super.onFinishInflate() @@ -21,12 +23,16 @@ class CatsView @JvmOverloads constructor( } } - override fun populate(fact: Fact) { - findViewById(R.id.fact_textView).text = fact.fact + override fun populate(data: ModelPresentation) { + findViewById(R.id.fact_textView).text = data.fact.fact + val im = findViewById(R.id.image) + Picasso.get() + .load(data.imageCat.url) + .into(im) } } interface ICatsView { - fun populate(fact: Fact) -} \ No newline at end of file + fun populate(data: ModelPresentation) +} diff --git a/app/src/main/java/otus/homework/coroutines/CatsViewModel.kt b/app/src/main/java/otus/homework/coroutines/CatsViewModel.kt new file mode 100644 index 00000000..3bd49546 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatsViewModel.kt @@ -0,0 +1,65 @@ +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.CoroutineName +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import java.net.SocketTimeoutException + +class CatsViewModel( + private val catsService: CatsService, + private val imageCatService: ImageCatService +) : ViewModel() { + + private val _catsModel = MutableLiveData() + val catsModel: LiveData = _catsModel + + private val handler = CoroutineExceptionHandler { coroutineContext, throwable -> + if (throwable is SocketTimeoutException) _catsModel.value = Error + else CrashMonitor.trackWarning() + } + init { + onInitComplete() + } + + fun onInitComplete() { + viewModelScope.launch(handler + CoroutineName("CatsCoroutine")) { + val fact = async { catsService.getCatFact() } + val image = async { imageCatService.getCatImage() } + + val responseFact = fact.await() + val responseImage = image.await() + if (responseFact.isSuccessful && responseFact.body() != null + && responseImage.isSuccessful && responseImage.body() != null + ) { + _catsModel.value = Success( + ModelPresentation( + responseFact.body()!!, + responseImage.body()!!.first() + ) + ) + } + } + } + +} + + +class CatsViewModelFactory( + private val catsService: CatsService, + private val imageCatService: ImageCatService +) : + ViewModelProvider.NewInstanceFactory() { + + override fun create(modelClass: Class): T = + CatsViewModel(catsService, imageCatService) as T +} + +sealed class Result +data class Success(val data: ModelPresentation) : Result() +object Error : Result() diff --git a/app/src/main/java/otus/homework/coroutines/DiContainer.kt b/app/src/main/java/otus/homework/coroutines/DiContainer.kt index 23ddc3b2..cfa4cbdf 100644 --- a/app/src/main/java/otus/homework/coroutines/DiContainer.kt +++ b/app/src/main/java/otus/homework/coroutines/DiContainer.kt @@ -13,4 +13,13 @@ class DiContainer { } val service by lazy { retrofit.create(CatsService::class.java) } -} \ No newline at end of file + + private val retrofit2 by lazy { + Retrofit.Builder() + .baseUrl("https://api.thecatapi.com/v1/images/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + + val service2 by lazy { retrofit2.create(ImageCatService::class.java) } +} diff --git a/app/src/main/java/otus/homework/coroutines/ImageCat.kt b/app/src/main/java/otus/homework/coroutines/ImageCat.kt new file mode 100644 index 00000000..4bcd985d --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/ImageCat.kt @@ -0,0 +1,14 @@ +package otus.homework.coroutines + +import com.google.gson.annotations.SerializedName + +data class ImageCat( + @field:SerializedName("id") + val id: String, + @field:SerializedName("url") + val url: String, + @field:SerializedName("width") + val width: Int, + @field:SerializedName("height") + val height: Int +) diff --git a/app/src/main/java/otus/homework/coroutines/ImageCatService.kt b/app/src/main/java/otus/homework/coroutines/ImageCatService.kt new file mode 100644 index 00000000..16271d52 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/ImageCatService.kt @@ -0,0 +1,10 @@ +package otus.homework.coroutines + +import retrofit2.Response +import retrofit2.http.GET + +interface ImageCatService { + + @GET("search") + suspend fun getCatImage() : Response> +} diff --git a/app/src/main/java/otus/homework/coroutines/MainActivity.kt b/app/src/main/java/otus/homework/coroutines/MainActivity.kt index a9dafb3b..8b536d08 100644 --- a/app/src/main/java/otus/homework/coroutines/MainActivity.kt +++ b/app/src/main/java/otus/homework/coroutines/MainActivity.kt @@ -1,13 +1,19 @@ package otus.homework.coroutines -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.widget.Toast +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { - lateinit var catsPresenter: CatsPresenter - private val diContainer = DiContainer() + private val catsViewModel by viewModels { + CatsViewModelFactory( + diContainer.service, + diContainer.service2 + ) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -15,16 +21,17 @@ class MainActivity : AppCompatActivity() { 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.presenter = catsViewModel + + catsViewModel.catsModel.observe(this) { result -> + when (result) { + is Success -> view.populate(result.data) + Error -> Toast.makeText( + this, + "Не удалось получить ответ от сервером", + Toast.LENGTH_LONG + ).show() + } } - super.onStop() } -} \ No newline at end of file +} diff --git a/app/src/main/java/otus/homework/coroutines/ModelPresentation.kt b/app/src/main/java/otus/homework/coroutines/ModelPresentation.kt new file mode 100644 index 00000000..231f92e1 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/ModelPresentation.kt @@ -0,0 +1,6 @@ +package otus.homework.coroutines + +data class ModelPresentation( + val fact: Fact, + val imageCat: ImageCat +) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9508066d..a30c87cc 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -27,4 +27,12 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/fact_textView" /> - \ No newline at end of file + + + diff --git a/build.gradle b/build.gradle index 9f4fb9aa..a20d508c 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.2.1' + classpath 'com.android.tools.build:gradle:8.1.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -23,4 +23,4 @@ allprojects { task clean(type: Delete) { delete rootProject.buildDir -} \ No newline at end of file +}