Skip to content

Coroutines homework done #158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ plugins {
}

android {
compileSdkVersion 30
compileSdkVersion 33
buildToolsVersion "30.0.3"

defaultConfig {
applicationId "otus.homework.coroutines"
minSdkVersion 23
targetSdkVersion 30
targetSdkVersion 33
versionCode 1
versionName "1.0"

Expand All @@ -33,6 +33,8 @@ android {
}

dependencies {
def coroutines_version = '1.6.3'

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
Expand All @@ -42,4 +44,7 @@ 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 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
}
14 changes: 14 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatDetails.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package otus.homework.coroutines

import com.google.gson.annotations.SerializedName

data class CatDetails(
@field:SerializedName("id")
val id: String,
@field:SerializedName("created_at")
val createdAt: String,
@field:SerializedName("tags")
val tags: List<String> = emptyList(),
@field:SerializedName("url")
val url: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package otus.homework.coroutines


data class CatFactWithImage(
val fact: String,
val imageUrl: String
)
10 changes: 10 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatImageService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package otus.homework.coroutines

import retrofit2.http.GET
import retrofit2.http.Query

interface CatImageService {

@GET("cat")
suspend fun getCatImage(@Query("json") json: Boolean = true): CatDetails
}
39 changes: 22 additions & 17 deletions app/src/main/java/otus/homework/coroutines/CatsPresenter.kt
Original file line number Diff line number Diff line change
@@ -1,35 +1,40 @@
package otus.homework.coroutines

import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
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 catImageService: CatImageService
) {

private val tag = this.javaClass.simpleName
private var _catsView: ICatsView? = null
private val scope = PresenterScope()

fun onInitComplete() {
catsService.getCatFact().enqueue(object : Callback<Fact> {

override fun onResponse(call: Call<Fact>, response: Response<Fact>) {
if (response.isSuccessful && response.body() != null) {
_catsView?.populate(response.body()!!)
}
}

override fun onFailure(call: Call<Fact>, t: Throwable) {
CrashMonitor.trackWarning()
scope.launch {
try {
val fact = async { catsService.getCatFact() }
val image = async { catImageService.getCatImage() }
val catInfo = CatFactWithImage(fact.await().fact, image.await().url)
_catsView?.populate(catInfo)
} catch (e: SocketTimeoutException) {
_catsView?.showError(R.string.error_failed_to_get_response_from_server)
} catch (e: Exception) {
_catsView?.showError(e.message)
CrashMonitor.trackWarning(tag, e)
}
})
}
}

fun attachView(catsView: ICatsView) {
_catsView = catsView
}

fun detachView() {
scope.cancel()
_catsView = null
}
}
}
5 changes: 2 additions & 3 deletions app/src/main/java/otus/homework/coroutines/CatsService.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package otus.homework.coroutines

import retrofit2.Call
import retrofit2.http.GET

interface CatsService {

@GET("fact")
fun getCatFact() : Call<Fact>
}
suspend fun getCatFact(): Fact
}
30 changes: 25 additions & 5 deletions app/src/main/java/otus/homework/coroutines/CatsView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.annotation.StringRes
import androidx.constraintlayout.widget.ConstraintLayout
import com.squareup.picasso.Picasso

class CatsView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), ICatsView {

var presenter :CatsPresenter? = null
var presenter: CatsPresenter? = null

override fun onFinishInflate() {
super.onFinishInflate()
Expand All @@ -21,12 +25,28 @@ class CatsView @JvmOverloads constructor(
}
}

override fun populate(fact: Fact) {
findViewById<TextView>(R.id.fact_textView).text = fact.text
override fun populate(fact: CatFactWithImage) {
findViewById<TextView>(R.id.fact_textView).text = fact.fact
val url = "https://cataas.com${fact.imageUrl}"
Picasso.get().load(url).into(
findViewById<ImageView>(R.id.cat_imageView)
)
}

override fun showError(error: String?) {
val errorText = error ?: context.getString(R.string.error_unknown)
Toast.makeText(context, errorText, Toast.LENGTH_SHORT).show()
}

override fun showError(@StringRes error: Int) {
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
}
}

interface ICatsView {

fun populate(fact: Fact)
}
fun populate(fact: CatFactWithImage)

fun showError(error: String?)
fun showError(@StringRes error: Int)
}
39 changes: 39 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatsViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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

class CatsViewModel(
private val catsService: CatsService,
private val catImageService: CatImageService
) : ViewModel() {

private var _stateLiveData = MutableLiveData<Result<CatFactWithImage>>()
val stateLiveData: LiveData<Result<CatFactWithImage>> get() = _stateLiveData

init {
loadData()
}

fun loadData() {
viewModelScope.launch(CoroutineExceptionHandler { _, throwable ->
_stateLiveData.value = Result.Error(Error(throwable.message))
CrashMonitor.trackWarning("", throwable)
}) {
try {
val fact = async { catsService.getCatFact() }
val image = async { catImageService.getCatImage() }
val catInfo = CatFactWithImage(fact.await().fact, image.await().url)
_stateLiveData.value = Result.Success(catInfo)
} catch (e: Exception) {
_stateLiveData.value = Result.Error(Error("Не удалось получить ответ от сервера"))
CrashMonitor.trackWarning("", e)
}
}
}
}
15 changes: 15 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatsViewModelFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package otus.homework.coroutines

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class CatsViewModelFactory : ViewModelProvider.Factory {

private val diContainer = DiContainer()
private val catsService: CatsService = diContainer.catService
private val catImageService: CatImageService = diContainer.catImageService

override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CatsViewModel(catsService, catImageService) as T
}
}
7 changes: 5 additions & 2 deletions app/src/main/java/otus/homework/coroutines/CrashMonitor.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package otus.homework.coroutines

import android.util.Log

object CrashMonitor {

/**
* Pretend this is Crashlytics/AppCenter
*/
fun trackWarning() {
fun trackWarning(tag: String, t: Throwable) {
Log.e(tag, t.message ?: "Unknown error")
}
}
}
15 changes: 12 additions & 3 deletions app/src/main/java/otus/homework/coroutines/DiContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@ import retrofit2.converter.gson.GsonConverterFactory

class DiContainer {

private val retrofit by lazy {
private val catFactRetrofit by lazy {
Retrofit.Builder()
.baseUrl("https://catfact.ninja/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}

val service by lazy { retrofit.create(CatsService::class.java) }
}
val catService by lazy { catFactRetrofit.create(CatsService::class.java) }

private val catImageRetrofit by lazy {
Retrofit.Builder()
.baseUrl("https://cataas.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}

val catImageService by lazy { catImageRetrofit.create(CatImageService::class.java) }
}
24 changes: 5 additions & 19 deletions app/src/main/java/otus/homework/coroutines/Fact.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,8 @@ package otus.homework.coroutines
import com.google.gson.annotations.SerializedName

data class Fact(
@field:SerializedName("createdAt")
val createdAt: String,
@field:SerializedName("deleted")
val deleted: Boolean,
@field:SerializedName("_id")
val id: String,
@field:SerializedName("text")
val text: String,
@field:SerializedName("source")
val source: String,
@field:SerializedName("used")
val used: Boolean,
@field:SerializedName("type")
val type: String,
@field:SerializedName("user")
val user: String,
@field:SerializedName("updatedAt")
val updatedAt: String
)
@field:SerializedName("fact")
val fact: String,
@field:SerializedName("length")
val length: Int
)
43 changes: 30 additions & 13 deletions app/src/main/java/otus/homework/coroutines/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,46 @@ package otus.homework.coroutines

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider

class MainActivity : AppCompatActivity() {

lateinit var catsPresenter: CatsPresenter
// lateinit var catsPresenter: CatsPresenter
// private val diContainer = DiContainer()

private val diContainer = DiContainer()
lateinit var vm: CatsViewModel

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()
vm = ViewModelProvider(this, CatsViewModelFactory()).get(CatsViewModel::class.java)
vm.stateLiveData.observe(this) { result ->
when (result) {
is Result.Success -> {
view.populate(result.value)
}

is Result.Error -> {
view.showError(result.throwable.toString())
}
}
}
super.onStop()

view.setOnClickListener { vm.loadData() }

// catsPresenter = CatsPresenter(diContainer.catService, diContainer.catImageService)
// view.presenter = catsPresenter
// catsPresenter.attachView(view)
// catsPresenter.onInitComplete()
}
}

// override fun onStop() {
// if (isFinishing) {
// catsPresenter.detachView()
// }
// super.onStop()
// }
}
Loading