Skip to content
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
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ android {
}

dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7"
implementation "androidx.fragment:fragment-ktx:1.8.5"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatLoader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package otus.homework.coroutines

interface CatLoader {

fun onInitComplete(): Unit

}
11 changes: 11 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatsFactService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package otus.homework.coroutines

import otus.homework.coroutines.dto.Fact
import retrofit2.http.GET

interface CatsFactService {

@GET("fact")
suspend fun getCatFact() : Fact

}
11 changes: 11 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatsImageService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package otus.homework.coroutines

import otus.homework.coroutines.dto.CatImage
import retrofit2.http.GET

interface CatsImageService {

@GET("v1/images/search")
suspend fun getCatImage() : List<CatImage>

}
35 changes: 21 additions & 14 deletions app/src/main/java/otus/homework/coroutines/CatsPresenter.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
package otus.homework.coroutines

import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import otus.homework.coroutines.dto.CatImage


class CatsPresenter(
private val catsService: CatsService
) {
private val catsFactService: CatsFactService,
private val catsImagesService: CatsImageService
): CatLoader {

private var _catsView: ICatsView? = null

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

PresenterScope.launch {
try {

val catFact = async { catsFactService.getCatFact() }.await()

override fun onResponse(call: Call<Fact>, response: Response<Fact>) {
if (response.isSuccessful && response.body() != null) {
_catsView?.populate(response.body()!!)
val catImage: List<CatImage> = async { catsImagesService.getCatImage() }.await()

_catsView?.populate(catFact, catImage.first())
} catch (ex: Exception) {
if (ex is java.net.SocketTimeoutException) {
_catsView?.onError("Unable to get response from server")
}
CrashMonitor.trackWarning(ex.message!!)
}
}

override fun onFailure(call: Call<Fact>, t: Throwable) {
CrashMonitor.trackWarning()
}
})
}

fun attachView(catsView: ICatsView) {
Expand Down
21 changes: 18 additions & 3 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,22 @@ 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
import otus.homework.coroutines.dto.CatImage
import otus.homework.coroutines.dto.Fact
import otus.homework.coroutines.vm.CatsPresenterViewModel

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

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

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

override fun populate(fact: Fact) {
override fun populate(fact: Fact, image: CatImage) {
findViewById<TextView>(R.id.fact_textView).text = fact.fact
Picasso.get().load(image.url).into(findViewById<ImageView>(R.id.cat_imageView));
}

override fun onError(text: String) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
}

}

interface ICatsView {

fun populate(fact: Fact)
fun populate(fact: Fact, image: CatImage)

fun onError(text: String)

}
5 changes: 4 additions & 1 deletion 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 {

private final val TAG = javaClass.simpleName
/**
* Pretend this is Crashlytics/AppCenter
*/
fun trackWarning() {
fun trackWarning(text: String) {
Log.e(TAG, text)
}
}
14 changes: 12 additions & 2 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,22 @@ import retrofit2.converter.gson.GsonConverterFactory

class DiContainer {

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

val service by lazy { retrofit.create(CatsService::class.java) }
val catsFactService by lazy { catsFactRetrofit.create(CatsFactService::class.java) }

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

val catsImagesService by lazy { catsImagesRetrofit.create(CatsImageService::class.java) }

}
36 changes: 32 additions & 4 deletions app/src/main/java/otus/homework/coroutines/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,51 @@ package otus.homework.coroutines

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import kotlinx.coroutines.CoroutineExceptionHandler
import otus.homework.coroutines.dto.Result
import otus.homework.coroutines.vm.CatsPresenterViewModel

class MainActivity : AppCompatActivity() {

lateinit var catsPresenter: CatsPresenter

private val diContainer = DiContainer()

private val catsPresenterViewModel: CatsPresenterViewModel by viewModels {
CatsPresenterViewModel.provideFactory(diContainer.catsFactService, diContainer.catsImagesService, this)
}

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()
// catsPresenter = CatsPresenter(diContainer.catsFactService, diContainer.catsImagesService)
// view.presenter = catsPresenter
// catsPresenter.attachView(view)
// catsPresenter.onInitComplete()

catsPresenterViewModel.onInitComplete()
view.presenter = catsPresenterViewModel

catsPresenterViewModel.catImageFact.observe(this, Observer { result ->
when (result) {
is Result.Success -> {
val image = result.data.first
val fact = result.data.second
view.populate(fact, image)
}

is Result.Error ->
view.onError(result.message!!)
}

})

}

override fun onStop() {
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/otus/homework/coroutines/PresenterScope.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package otus.homework.coroutines

import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlin.coroutines.CoroutineContext

object PresenterScope : CoroutineScope, AutoCloseable {

private val job = Job()

override val coroutineContext: CoroutineContext
get() {
return Dispatchers.Main + job + CoroutineName("CatsCoroutine")
}

override fun close() {
job.cancel()
}
}
14 changes: 14 additions & 0 deletions app/src/main/java/otus/homework/coroutines/dto/CatImage.kt
Original file line number Diff line number Diff line change
@@ -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: Int,
@field:SerializedName("height")
val height: Int
)
10 changes: 10 additions & 0 deletions app/src/main/java/otus/homework/coroutines/dto/Fact.kt
Original file line number Diff line number Diff line change
@@ -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
)
6 changes: 6 additions & 0 deletions app/src/main/java/otus/homework/coroutines/dto/Result.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package otus.homework.coroutines.dto

sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String?, val throwable: Throwable? = null) : Result<Nothing>()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package otus.homework.coroutines.vm

import android.os.Bundle
import androidx.lifecycle.*
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.savedstate.SavedStateRegistryOwner
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
import otus.homework.coroutines.CatLoader
import otus.homework.coroutines.dto.CatImage
import otus.homework.coroutines.CatsFactService
import otus.homework.coroutines.CatsImageService
import otus.homework.coroutines.CrashMonitor
import otus.homework.coroutines.dto.Fact
import otus.homework.coroutines.dto.Result
import kotlin.Pair

class CatsPresenterViewModel(
private val catsFactService: CatsFactService,
private val catsImagesService: CatsImageService
) : ViewModel(), CatLoader {

companion object {
fun provideFactory(catsFactService: CatsFactService,
catsImagesService: CatsImageService,
owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null,): AbstractSavedStateViewModelFactory =object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
): T {
return CatsPresenterViewModel(catsFactService,catsImagesService) as T
}
}
}

private val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
CrashMonitor.trackWarning(throwable.message.toString())
_catImageFact.postValue(Result.Error("", throwable))
}


private val _catImageFact = MutableLiveData<Result<Pair<CatImage, Fact>>>()
val catImageFact: LiveData<Result<Pair<CatImage, Fact>>> get() = _catImageFact

override fun onInitComplete() {
viewModelScope.launch(Dispatchers.IO + coroutineExceptionHandler) {
try {
val imageResponse = async{ catsImagesService.getCatImage()}
val factResponse = async{ catsFactService.getCatFact()}

val awaitAll = awaitAll(imageResponse, factResponse)

val image = awaitAll[0] as List<CatImage>
val fact = awaitAll[1] as Fact

_catImageFact.postValue(Result.Success(Pair(image[0], fact)))

} catch (e: Exception) {
e.printStackTrace()
_catImageFact.postValue(Result.Error("", e))
}
}
}
}
11 changes: 10 additions & 1 deletion app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
android:layout_height="match_parent"
tools:context=".MainActivity">

<ImageView
android:id="@+id/cat_imageView"
android:layout_width="300dp"
android:layout_height="300dp"
android:scaleType="centerCrop"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />

<TextView
android:id="@+id/fact_textView"
android:textColor="@color/black"
Expand All @@ -16,7 +25,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/cat_imageView" />

<Button
android:id="@+id/button"
Expand Down