diff --git a/app/build.gradle b/app/build.gradle index 679dbba4..24a77c92 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,4 +42,6 @@ dependencies { implementation 'com.google.android.material:material:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'com.squareup.picasso:picasso:2.71828' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/CatImage.kt b/app/src/main/java/otus/homework/coroutines/CatImage.kt new file mode 100644 index 00000000..ed889890 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatImage.kt @@ -0,0 +1,8 @@ +package otus.homework.coroutines + +import com.google.gson.annotations.SerializedName + +data class CatImage( + @field:SerializedName("file") + val fileUrl: String +) diff --git a/app/src/main/java/otus/homework/coroutines/CatsModel.kt b/app/src/main/java/otus/homework/coroutines/CatsModel.kt new file mode 100644 index 00000000..c8f9e620 --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatsModel.kt @@ -0,0 +1,6 @@ +package otus.homework.coroutines + +data class CatsModel( + val fact: Fact, + val image: CatImage +) diff --git a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt b/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt index e4b05120..d7dc2b53 100644 --- a/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt +++ b/app/src/main/java/otus/homework/coroutines/CatsPresenter.kt @@ -1,28 +1,35 @@ package otus.homework.coroutines -import retrofit2.Call -import retrofit2.Callback +import kotlinx.coroutines.* import retrofit2.Response +import java.lang.Exception +import java.net.SocketTimeoutException class CatsPresenter( - private val catsService: CatsService + private val catsService: CatsService, + private val imageService: ImageService ) { private var _catsView: ICatsView? = null + private var 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 fact = async { handleResponse(catsService.getCatFact()) + } + val image = async { handleResponse(imageService.getRandomImage()) } + _catsView?.populate(CatsModel(fact.await(), image.await())) + } catch (ex: Exception) { + when (ex) { + is SocketTimeoutException -> _catsView?.showNoResponseToast() + else -> { + _catsView?.showToast(ex.message ?: "Error") + CrashMonitor.trackWarning() + } } } - - override fun onFailure(call: Call, t: Throwable) { - CrashMonitor.trackWarning() - } - }) + } } fun attachView(catsView: ICatsView) { @@ -32,4 +39,8 @@ class CatsPresenter( fun detachView() { _catsView = null } + + fun cancelCatsJob() { + 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 index 1e11c382..ea143862 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("random?animal_type=cat") - fun getCatFact() : Call + suspend fun getCatFact() : Response } \ 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..b78f36c6 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, @@ -21,12 +24,26 @@ class CatsView @JvmOverloads constructor( } } - override fun populate(fact: Fact) { - findViewById(R.id.fact_textView).text = fact.text + override fun populate(model: CatsModel) { + findViewById(R.id.fact_textView).text = model.fact.text + Picasso.get() + .load(model.image.fileUrl) + .fit() + .centerInside() + .into(findViewById(R.id.imageView)) + } + + override fun showToast(message: String) { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } + + override fun showNoResponseToast() { + showToast(context.getString(R.string.no_server_response)) } } interface ICatsView { - - fun populate(fact: Fact) + fun populate(model: CatsModel) + fun showToast(message: String) + fun showNoResponseToast() } \ 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..e81b253f --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/CatsViewModel.kt @@ -0,0 +1,51 @@ +package otus.homework.coroutines + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import java.lang.Exception +import java.net.SocketTimeoutException + +sealed class Result + +class Success(val data: T) : Result() +class Error(val errorMsg: String) : Result() + +class CatsViewModel( + private val catsService: CatsService, + private val imageService: ImageService +) : ViewModel() { + + private val catInfo: MutableLiveData = MutableLiveData() + private val exceptionHandler = CoroutineExceptionHandler() { _, exception -> + when(exception){ + is SocketTimeoutException -> catInfo.value = Error(exception.localizedMessage ?: "An error occurred") + else -> CrashMonitor.trackWarning() + } + } + + init { + loadInfo() + } + + fun getCatInfo(): LiveData { + return catInfo + } + + private fun loadInfo() { + viewModelScope.launch(exceptionHandler) { + val fact = async { handleResponse(catsService.getCatFact()) } + val image = async { handleResponse(imageService.getRandomImage()) } + catInfo.value = Success(CatsModel(fact.await(), image.await())) + } + } + + fun onButtonClicked() { + loadInfo() + } + +} \ 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 bb480491..5adcac07 100644 --- a/app/src/main/java/otus/homework/coroutines/DiContainer.kt +++ b/app/src/main/java/otus/homework/coroutines/DiContainer.kt @@ -12,5 +12,13 @@ class DiContainer { .build() } + private val imageRetrofit by lazy { + Retrofit.Builder() + .baseUrl("https://aws.random.cat/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + } + val service by lazy { retrofit.create(CatsService::class.java) } + val imageService by lazy { imageRetrofit.create(ImageService::class.java) } } \ No newline at end of file diff --git a/app/src/main/java/otus/homework/coroutines/ImageService.kt b/app/src/main/java/otus/homework/coroutines/ImageService.kt new file mode 100644 index 00000000..896cf67d --- /dev/null +++ b/app/src/main/java/otus/homework/coroutines/ImageService.kt @@ -0,0 +1,16 @@ +package otus.homework.coroutines + +import retrofit2.Response +import retrofit2.http.GET +import java.lang.Exception + + +fun handleResponse(response: Response): T{ + if (response.isSuccessful) return response.body()!! + else throw Exception() +} + +interface ImageService { + @GET("meow") + suspend fun getRandomImage() : Response +} \ 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..03087322 100644 --- a/app/src/main/java/otus/homework/coroutines/MainActivity.kt +++ b/app/src/main/java/otus/homework/coroutines/MainActivity.kt @@ -2,29 +2,50 @@ package otus.homework.coroutines import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.widget.Button +import android.widget.ImageView +import android.widget.TextView +import com.squareup.picasso.Picasso class MainActivity : AppCompatActivity() { - lateinit var catsPresenter: CatsPresenter + lateinit var catsViewModel: CatsViewModel private val diContainer = DiContainer() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - 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() + setContentView(layoutInflater.inflate(R.layout.activity_main, null)) + + val textView = findViewById(R.id.fact_textView) + val imageView = findViewById(R.id.imageView) + findViewById