diff --git a/app/build.gradle b/app/build.gradle index 679dbba4..7af1b41a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" @@ -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' -} \ No newline at end of file + 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' +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fe34985b..1b268971 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,4 +20,4 @@ - \ No newline at end of file + 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..38e4dfd7 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatViewModel.kt @@ -0,0 +1,55 @@ +package otus.homework.coroutines + +import android.content.res.Resources +import androidx.lifecycle.LiveData +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.launch +import otus.homework.coroutines.model.CatModel +import java.net.SocketTimeoutException + + +class CatViewModel( + private val factService: CatsFactService, + private val imageService: CatsImageService +) : ViewModel() { + private val _catModel: MutableLiveData = MutableLiveData() + val catModel: LiveData = _catModel + + fun onInitComplete() { + loadData() + } + + private val handler = CoroutineExceptionHandler { _, throwable -> + val message = throwable.message.toString() + CrashMonitor.trackWarning(message) + } + + private fun loadData() { + viewModelScope.launch(handler) { + try { + val catFactJob = async { factService.getCatFact() } + val catImageJob = async { imageService.getCatImage() } + + _catModel.value = Result.Success( + CatModel( + catFactJob.await().fact, + CatsImageService.BASE_URL + catImageJob.await().url + ) + ) + } catch (exception: SocketTimeoutException) { + _catModel.value = Result.Error( + Throwable( + Resources.getSystem().getString(R.string.error_connection) + ) + ) + } catch (exception: CancellationException) { + throw exception + } + } + } +} diff --git a/app/src/main/java/otus/homework/coroutines/CatsFactService.kt b/app/src/main/java/otus/homework/coroutines/CatsFactService.kt new file mode 100644 index 00000000..2fe18126 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatsFactService.kt @@ -0,0 +1,14 @@ +package otus.homework.coroutines + +import otus.homework.coroutines.model.Fact +import retrofit2.http.GET + +interface CatsFactService { + + @GET("fact") + suspend fun getCatFact() : Fact + + companion object { + const val BASE_URL = "https://catfact.ninja/" + } +} 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..c4d5097c --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatsImageService.kt @@ -0,0 +1,14 @@ +package otus.homework.coroutines + +import otus.homework.coroutines.model.RandomCat +import retrofit2.http.GET + +interface CatsImageService { + + @GET("cat?json=true") + suspend fun getCatImage() : RandomCat + + companion object { + const val BASE_URL = "https://cataas.com/" + } +} diff --git a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt b/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt index e4b05120..78cf10a7 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt @@ -1,28 +1,51 @@ 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 { - - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful && response.body() != null) { - _catsView?.populate(response.body()!!) + catsScope.launch { + try { + val catFactJob = async { factService.getCatFact() } + val catImageJob = async { imageService.getCatImage() } + + val catModel = CatModel( + catFactJob.await().fact, + CatsImageService.BASE_URL + catImageJob.await().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, t: Throwable) { - CrashMonitor.trackWarning() - } - }) + } } fun attachView(catsView: ICatsView) { @@ -31,5 +54,6 @@ class CatsPresenter( fun detachView() { _catsView = null + catsScope.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 deleted file mode 100644 index 479b2cfb..00000000 --- a/app/src/main/java/otus/homework/coroutines/CatsService.kt +++ /dev/null @@ -1,10 +0,0 @@ -package otus.homework.coroutines - -import retrofit2.Call -import retrofit2.http.GET - -interface CatsService { - - @GET("fact") - fun getCatFact() : Call -} \ 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 30ac2531..be0f7583 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsView.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsView.kt @@ -3,8 +3,13 @@ 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, @@ -12,21 +17,35 @@ class CatsView @JvmOverloads constructor( 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