diff --git a/app/build.gradle b/app/build.gradle index a414e0e8..81a55b82 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,7 +38,10 @@ dependencies { implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.google.code.gson:gson:2.10' implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.0' implementation 'com.google.android.material:material:1.11.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'com.squareup.picasso:picasso:2.71828' + implementation 'androidx.activity:activity-ktx:1.2.3' + } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CatInfo.kt b/app/src/main/java/otus/homework/coroutines/CatInfo.kt new file mode 100644 index 00000000..71980826 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatInfo.kt @@ -0,0 +1,6 @@ +package otus.homework.coroutines + +data class CatInfo( + val catFact: String, + val catImageUrl: String, +) 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/CatsScreenSatate.kt b/app/src/main/java/otus/homework/coroutines/CatsScreenSatate.kt new file mode 100644 index 00000000..9738c676 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatsScreenSatate.kt @@ -0,0 +1,8 @@ +package otus.homework.coroutines + +import androidx.annotation.StringRes + +sealed interface CatsScreenState { + data class Success(val data: T) : CatsScreenState + data class Error(@StringRes val message: Int, val th: Throwable) : CatsScreenState +} \ 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..7f71cc10 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsService.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsService.kt @@ -1,10 +1,15 @@ package otus.homework.coroutines -import retrofit2.Call +import otus.homework.coroutines.dto.CatImage +import otus.homework.coroutines.dto.Fact import retrofit2.http.GET +import retrofit2.http.Url interface CatsService { @GET("fact") - fun getCatFact() : Call + suspend fun getCatFact(): Fact + + @GET + suspend fun getCatImage(@Url url: String = "https://api.thecatapi.com/v1/images/search"): List } \ 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..9a038b68 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsView.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsView.kt @@ -3,8 +3,11 @@ 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, @@ -12,7 +15,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 +24,23 @@ class CatsView @JvmOverloads constructor( } } - override fun populate(fact: Fact) { - findViewById(R.id.fact_textView).text = fact.fact + override fun populate(state: CatsScreenState) { + when (state) { + is CatsScreenState.Success<*> -> { + (state.data as? CatInfo)?.let { catInfo -> + findViewById(R.id.fact_textView).text = catInfo.catFact + Picasso.get().load(catInfo.catImageUrl).into(findViewById(R.id.image)) + } + } + + is CatsScreenState.Error -> { + Toast.makeText(context, state.message, Toast.LENGTH_SHORT).show() + } + } + } } interface ICatsView { - - fun populate(fact: Fact) + fun populate(state: CatsScreenState) } \ No newline at end of file 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..5007571d --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatsViewModel.kt @@ -0,0 +1,59 @@ +package otus.homework.coroutines + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.cancel +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import java.net.SocketTimeoutException + +class CatsViewModel( + private val catsService: CatsService +) : ViewModel() { + + private var _catsView: ICatsView? = null + + private val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> + CrashMonitor.trackWarning(exception.message) + } + + fun onInitComplete() { + viewModelScope.launch(coroutineExceptionHandler) { + try { + coroutineScope { + val catFact = async(Dispatchers.IO) { catsService.getCatFact() } + val catImage = async(Dispatchers.IO) { catsService.getCatImage() } + val state = CatsScreenState.Success( + CatInfo( + catFact = catFact.await().fact, + catImageUrl = catImage.await().first().url + ) + ) + _catsView?.populate(state) + } + } catch (e: SocketTimeoutException) { + _catsView?.populate(CatsScreenState.Error(R.string.error_message, e)) + } + } + } + + fun attachView(catsView: ICatsView) { + _catsView = catsView + } + + + fun detachView() { + _catsView = null + viewModelScope.cancel() + } +} + +class CatsViewModelFactory(private val catsService: CatsService) : + ViewModelProvider.NewInstanceFactory() { + override fun create(modelClass: Class): T = + CatsViewModel(catsService) as T +} \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt b/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt index 32e6b018..00c199a8 100644 --- a/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt +++ b/app/src/main/java/otus/homework/coroutines/CrashMonitor.kt @@ -1,10 +1,13 @@ package otus.homework.coroutines +import android.util.Log + object CrashMonitor { /** * Pretend this is Crashlytics/AppCenter */ - fun trackWarning() { + fun trackWarning(throwableMessage: String?) { + Log.d("CATS_COROUTINE", "trackWarning: $throwableMessage") } } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/Fact.kt b/app/src/main/java/otus/homework/coroutines/Fact.kt deleted file mode 100644 index 643a5a33..00000000 --- a/app/src/main/java/otus/homework/coroutines/Fact.kt +++ /dev/null @@ -1,10 +0,0 @@ -package otus.homework.coroutines - -import com.google.gson.annotations.SerializedName - -data class Fact( - @field:SerializedName("fact") - val fact: String, - @field:SerializedName("length") - val length: Int -) \ 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..e38246fc 100644 --- a/app/src/main/java/otus/homework/coroutines/MainActivity.kt +++ b/app/src/main/java/otus/homework/coroutines/MainActivity.kt @@ -1,11 +1,16 @@ package otus.homework.coroutines -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { - lateinit var catsPresenter: CatsPresenter + private val catsViewModel: CatsViewModel by viewModels { + CatsViewModelFactory( + diContainer.service + ) + } private val diContainer = DiContainer() @@ -15,15 +20,14 @@ 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() + view.presenter = catsViewModel + catsViewModel.attachView(view) + catsViewModel.onInitComplete() } override fun onStop() { if (isFinishing) { - catsPresenter.detachView() + catsViewModel.detachView() } super.onStop() } diff --git a/app/src/main/java/otus/homework/coroutines/dto/CatImage.kt b/app/src/main/java/otus/homework/coroutines/dto/CatImage.kt new file mode 100644 index 00000000..e2c2af1a --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/dto/CatImage.kt @@ -0,0 +1,14 @@ +package otus.homework.coroutines.dto + +import com.google.gson.annotations.SerializedName + +data class CatImage( + @field:SerializedName("id") + val id: String, + @field:SerializedName("url") + val url: String, + @field:SerializedName("width") + val width: Long, + @field:SerializedName("height") + val height: Long, +) \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/dto/Fact.kt b/app/src/main/java/otus/homework/coroutines/dto/Fact.kt new file mode 100644 index 00000000..580bdb35 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/dto/Fact.kt @@ -0,0 +1,10 @@ +package otus.homework.coroutines.dto + +import com.google.gson.annotations.SerializedName + +data class Fact( + @field:SerializedName("fact") + val fact: String, + @field:SerializedName("length") + val length: Int +) \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9508066d..2f185818 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -3,28 +3,42 @@ 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"> - + + + + + + + + +