From 67aa8bd673454f3e7dce66c23092d912b341f5a0 Mon Sep 17 00:00:00 2001 From: Andrusov Date: Sun, 24 Nov 2024 23:29:51 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=92=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=94=D0=97=20Coroutines=20Homework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 1 + .../java/otus/homework/coroutines/CatModel.kt | 6 ++ .../otus/homework/coroutines/CatViewModel.kt | 62 +++++++++++++++++++ .../{CatsService.kt => CatsFactService.kt} | 5 +- .../homework/coroutines/CatsImageService.kt | 9 +++ .../otus/homework/coroutines/CatsPresenter.kt | 35 ----------- .../java/otus/homework/coroutines/CatsView.kt | 24 +++++-- .../otus/homework/coroutines/CrashMonitor.kt | 1 + .../otus/homework/coroutines/DiContainer.kt | 15 ++++- .../java/otus/homework/coroutines/Image.kt | 8 +++ .../otus/homework/coroutines/MainActivity.kt | 40 ++++++++++-- .../java/otus/homework/coroutines/Result.kt | 8 +++ app/src/main/res/layout/activity_main.xml | 15 ++++- app/src/main/res/values/strings.xml | 2 + build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 16 files changed, 177 insertions(+), 58 deletions(-) create mode 100644 app/src/main/java/otus/homework/coroutines/CatModel.kt create mode 100644 app/src/main/java/otus/homework/coroutines/CatViewModel.kt rename app/src/main/java/otus/homework/coroutines/{CatsService.kt => CatsFactService.kt} (50%) create mode 100644 app/src/main/java/otus/homework/coroutines/CatsImageService.kt delete mode 100644 app/src/main/java/otus/homework/coroutines/CatsPresenter.kt create mode 100644 app/src/main/java/otus/homework/coroutines/Image.kt create mode 100644 app/src/main/java/otus/homework/coroutines/Result.kt diff --git a/app/build.gradle b/app/build.gradle index a414e0e8..f5d863c3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,4 +41,5 @@ dependencies { implementation 'com.google.android.material:material:1.11.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'com.squareup.picasso:picasso:2.71828' + runtimeOnly 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7' } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CatModel.kt b/app/src/main/java/otus/homework/coroutines/CatModel.kt new file mode 100644 index 00000000..002ebbc4 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatModel.kt @@ -0,0 +1,6 @@ +package otus.homework.coroutines + +data class CatModel( + val fact: String, + val photoUrl: String, +) diff --git a/app/src/main/java/otus/homework/coroutines/CatViewModel.kt b/app/src/main/java/otus/homework/coroutines/CatViewModel.kt new file mode 100644 index 00000000..8ccf0de5 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatViewModel.kt @@ -0,0 +1,62 @@ +package otus.homework.coroutines + +import android.util.Log +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import java.net.SocketTimeoutException + +class CatViewModel( + private val catsFactService: CatsFactService, + private val catsImageService: CatsImageService, +) : ViewModel() { + + private var _catsView: ICatsView? = null + + private val _liveData = MutableLiveData() + val liveData = _liveData + + fun onInitComplete() { + PresenterScope.scope.launch(PresenterScope.exceptionHandler) { + try { + val success = Result.Success( + CatModel( + catsFactService.getCatFact().fact, + catsImageService.getCatImage().first().url + ) + ) + + _liveData.postValue(success) + } catch (e: SocketTimeoutException) { + _catsView?.toast("Не удалось получить ответ от сервером") + } catch (e: Throwable) { + _catsView?.toast(e.message) + throw e + } + + } + } + + fun attachView(catsView: ICatsView) { + _catsView = catsView + } + + fun detachView() { + _catsView = null + } +} + +object PresenterScope { + val exceptionHandler = CoroutineExceptionHandler { _, _ -> + CrashMonitor.trackWarning() + } + + val scope = CoroutineScope( + SupervisorJob() + Dispatchers.Main + CoroutineName("CatsCoroutine") + ) +} diff --git a/app/src/main/java/otus/homework/coroutines/CatsService.kt b/app/src/main/java/otus/homework/coroutines/CatsFactService.kt similarity index 50% rename from app/src/main/java/otus/homework/coroutines/CatsService.kt rename to app/src/main/java/otus/homework/coroutines/CatsFactService.kt index 479b2cfb..6ebf37db 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsService.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsFactService.kt @@ -1,10 +1,9 @@ package otus.homework.coroutines -import retrofit2.Call import retrofit2.http.GET -interface CatsService { +interface CatsFactService { @GET("fact") - fun getCatFact() : Call + suspend fun getCatFact() : Fact } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CatsImageService.kt b/app/src/main/java/otus/homework/coroutines/CatsImageService.kt new file mode 100644 index 00000000..d055114c --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatsImageService.kt @@ -0,0 +1,9 @@ +package otus.homework.coroutines + +import retrofit2.http.GET + +interface CatsImageService { + + @GET("/v1/images/search") + suspend fun getCatImage() : List +} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt b/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt deleted file mode 100644 index e4b05120..00000000 --- a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt +++ /dev/null @@ -1,35 +0,0 @@ -package otus.homework.coroutines - -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response - -class CatsPresenter( - private val catsService: CatsService -) { - - private var _catsView: ICatsView? = null - - fun onInitComplete() { - catsService.getCatFact().enqueue(object : Callback { - - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful && response.body() != null) { - _catsView?.populate(response.body()!!) - } - } - - override fun onFailure(call: Call, t: Throwable) { - CrashMonitor.trackWarning() - } - }) - } - - fun attachView(catsView: ICatsView) { - _catsView = catsView - } - - fun detachView() { - _catsView = null - } -} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CatsView.kt b/app/src/main/java/otus/homework/coroutines/CatsView.kt index be04b2a8..96bc3d27 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsView.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsView.kt @@ -3,16 +3,20 @@ 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 + defStyleAttr: Int = 0, ) : ConstraintLayout(context, attrs, defStyleAttr), ICatsView { - var presenter :CatsPresenter? = null + var presenter: CatViewModel? = null + var viewModel: CatViewModel? = null override fun onFinishInflate() { super.onFinishInflate() @@ -21,12 +25,20 @@ class CatsView @JvmOverloads constructor( } } - override fun populate(fact: Fact) { - findViewById(R.id.fact_textView).text = fact.fact + override fun populate(catModel: CatModel) { + findViewById(R.id.fact_textView).text = catModel.fact + Picasso.get() + .load(catModel.photoUrl) + .into(findViewById(R.id.imageView)) + } + + override fun toast(message: String?) { + Toast.makeText(context, message ?: "unknown error", Toast.LENGTH_LONG).show() } } interface ICatsView { - fun populate(fact: Fact) -} \ No newline at end of file + fun populate(catModel: CatModel) + fun toast(message: String?) +} diff --git a/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt b/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt index 32e6b018..bc4c3502 100644 --- a/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt +++ b/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt @@ -6,5 +6,6 @@ object CrashMonitor { * Pretend this is Crashlytics/AppCenter */ fun trackWarning() { + println("Warning tracked!") } } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/DiContainer.kt b/app/src/main/java/otus/homework/coroutines/DiContainer.kt index 23ddc3b2..32b58cc9 100644 --- a/app/src/main/java/otus/homework/coroutines/DiContainer.kt +++ b/app/src/main/java/otus/homework/coroutines/DiContainer.kt @@ -5,12 +5,21 @@ import retrofit2.converter.gson.GsonConverterFactory class DiContainer { - private val retrofit by lazy { + private val catsFactClient by lazy { Retrofit.Builder() .baseUrl("https://catfact.ninja/") .addConverterFactory(GsonConverterFactory.create()) .build() } - val service by lazy { retrofit.create(CatsService::class.java) } -} \ No newline at end of file + private val catsImageClient by lazy { + Retrofit.Builder() + .baseUrl("https://api.thecatapi.com") + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + + val catsFactService: CatsFactService by lazy { catsFactClient.create(CatsFactService::class.java) } + + val catsImageService: CatsImageService by lazy { catsImageClient.create(CatsImageService::class.java) } +} diff --git a/app/src/main/java/otus/homework/coroutines/Image.kt b/app/src/main/java/otus/homework/coroutines/Image.kt new file mode 100644 index 00000000..224f251a --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/Image.kt @@ -0,0 +1,8 @@ +package otus.homework.coroutines + +import com.google.gson.annotations.SerializedName + +data class Image( + @field:SerializedName("url") + val url: String, +) \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/MainActivity.kt b/app/src/main/java/otus/homework/coroutines/MainActivity.kt index a9dafb3b..e947a29e 100644 --- a/app/src/main/java/otus/homework/coroutines/MainActivity.kt +++ b/app/src/main/java/otus/homework/coroutines/MainActivity.kt @@ -1,11 +1,14 @@ package otus.homework.coroutines -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import kotlinx.coroutines.cancel class MainActivity : AppCompatActivity() { - lateinit var catsPresenter: CatsPresenter + private lateinit var catsViewModel: CatViewModel private val diContainer = DiContainer() @@ -15,16 +18,41 @@ class MainActivity : AppCompatActivity() { val view = layoutInflater.inflate(R.layout.activity_main, null) as CatsView setContentView(view) - catsPresenter = CatsPresenter(diContainer.service) + catsViewModel = + ViewModelProvider(this, ViewModelFactory(diContainer)).get(CatViewModel::class.java) + + /*catsPresenter = CatViewModel(diContainer.catsFactService, diContainer.catsImageService) view.presenter = catsPresenter catsPresenter.attachView(view) - catsPresenter.onInitComplete() + catsPresenter.onInitComplete()*/ + + view.presenter = catsViewModel + catsViewModel.attachView(view) + catsViewModel.onInitComplete() + + catsViewModel.liveData.observe(this) { + when (it) { + is Result.Success -> view.populate(it.catModel) + is Result.Error -> view.toast(it.message) + } + } } override fun onStop() { if (isFinishing) { - catsPresenter.detachView() + PresenterScope.scope.cancel() + //catsPresenter.detachView() + catsViewModel.detachView() } super.onStop() } -} \ No newline at end of file + + class ViewModelFactory(private val diContainer: DiContainer) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(CatViewModel::class.java)) { + return CatViewModel(diContainer.catsFactService, diContainer.catsImageService) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } + } +} diff --git a/app/src/main/java/otus/homework/coroutines/Result.kt b/app/src/main/java/otus/homework/coroutines/Result.kt new file mode 100644 index 00000000..560d1df5 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/Result.kt @@ -0,0 +1,8 @@ +package otus.homework.coroutines + + +sealed class Result { + + data class Success(val catModel: CatModel) : Result() + data class Error(val message: String) : Result() +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9508066d..02cc4cc1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -3,16 +3,16 @@ 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"> + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6c270d5d..465173a2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,6 @@ Cat Facts More Facts + + Не удалось получить ответ от сервером \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9f4fb9aa..207c6acb 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.6.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 32f256d6..5896ccfb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Jan 15 23:17:01 GST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 733449af4e348b1dcd89c806bd67ed07c4b416bd Mon Sep 17 00:00:00 2001 From: Andrusov Date: Wed, 11 Dec 2024 13:34:01 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=92=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=94=D0=97=20Coroutines=20Homework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otus/homework/coroutines/CatViewModel.kt | 38 +++++++------------ .../java/otus/homework/coroutines/Result.kt | 2 +- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/otus/homework/coroutines/CatViewModel.kt b/app/src/main/java/otus/homework/coroutines/CatViewModel.kt index 8ccf0de5..bd624e81 100644 --- a/app/src/main/java/otus/homework/coroutines/CatViewModel.kt +++ b/app/src/main/java/otus/homework/coroutines/CatViewModel.kt @@ -1,13 +1,9 @@ package otus.homework.coroutines -import android.util.Log import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import java.net.SocketTimeoutException @@ -21,21 +17,25 @@ class CatViewModel( private val _liveData = MutableLiveData() val liveData = _liveData + private val exceptionHandler = CoroutineExceptionHandler { _, _ -> + CrashMonitor.trackWarning() + } + fun onInitComplete() { - PresenterScope.scope.launch(PresenterScope.exceptionHandler) { + viewModelScope.launch(exceptionHandler) { try { - val success = Result.Success( - CatModel( - catsFactService.getCatFact().fact, - catsImageService.getCatImage().first().url + _liveData.postValue( + Result.Success( + CatModel( + catsFactService.getCatFact().fact, + catsImageService.getCatImage().first().url + ) ) ) - - _liveData.postValue(success) } catch (e: SocketTimeoutException) { - _catsView?.toast("Не удалось получить ответ от сервером") + _liveData.postValue(Result.Error("Не удалось получить ответ от сервером")) } catch (e: Throwable) { - _catsView?.toast(e.message) + _liveData.postValue(Result.Error(e.message)) throw e } @@ -50,13 +50,3 @@ class CatViewModel( _catsView = null } } - -object PresenterScope { - val exceptionHandler = CoroutineExceptionHandler { _, _ -> - CrashMonitor.trackWarning() - } - - val scope = CoroutineScope( - SupervisorJob() + Dispatchers.Main + CoroutineName("CatsCoroutine") - ) -} diff --git a/app/src/main/java/otus/homework/coroutines/Result.kt b/app/src/main/java/otus/homework/coroutines/Result.kt index 560d1df5..63d540ae 100644 --- a/app/src/main/java/otus/homework/coroutines/Result.kt +++ b/app/src/main/java/otus/homework/coroutines/Result.kt @@ -4,5 +4,5 @@ package otus.homework.coroutines sealed class Result { data class Success(val catModel: CatModel) : Result() - data class Error(val message: String) : Result() + data class Error(val message: String?) : Result() } From 75a768d2d148d5cdd31abb41d611a525554d079d Mon Sep 17 00:00:00 2001 From: Andrusov Date: Wed, 11 Dec 2024 13:45:09 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=92=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=94=D0=97=20Coroutines=20Homework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/otus/homework/coroutines/MainActivity.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/otus/homework/coroutines/MainActivity.kt b/app/src/main/java/otus/homework/coroutines/MainActivity.kt index e947a29e..11d37fb6 100644 --- a/app/src/main/java/otus/homework/coroutines/MainActivity.kt +++ b/app/src/main/java/otus/homework/coroutines/MainActivity.kt @@ -4,7 +4,6 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import kotlinx.coroutines.cancel class MainActivity : AppCompatActivity() { @@ -40,7 +39,6 @@ class MainActivity : AppCompatActivity() { override fun onStop() { if (isFinishing) { - PresenterScope.scope.cancel() //catsPresenter.detachView() catsViewModel.detachView() }