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
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ dependencies {
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.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
}
14 changes: 14 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatImage.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 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
)
6 changes: 6 additions & 0 deletions app/src/main/java/otus/homework/coroutines/CatModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package otus.homework.coroutines

data class CatModel (
val text: String?,
val imageUrl: String?
)
40 changes: 35 additions & 5 deletions app/src/main/java/otus/homework/coroutines/CatsPresenter.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,41 @@
package otus.homework.coroutines

import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import java.net.SocketTimeoutException

class CatsPresenter(
private val catsService: CatsService
private val catsService: CatsService,
private val imageService: ImageService,
private val onShowToast: (String?) -> Unit
) {

private var _catsView: ICatsView? = null
private val presenterScope = CoroutineScope(Dispatchers.Main + CoroutineName("CatsCoroutine"))
private var job: Job? = null

fun onInitComplete() {
job = presenterScope.launch {
try {
val fact = async {catsService.getCatFact()}
val image = async {imageService.getCatImage().firstOrNull()}
_catsView?.populate(CatModel(fact.await().fact, image.await()?.url))
}
catch (e: SocketTimeoutException)
{
onShowToast("Не удалось получить ответ от сервера")
}
catch (e: Exception)
{
onShowToast(e.message)
CrashMonitor.trackWarning(e.message)
}
}
/*
catsService.getCatFact().enqueue(object : Callback<Fact> {

override fun onResponse(call: Call<Fact>, response: Response<Fact>) {
Expand All @@ -22,7 +47,7 @@ class CatsPresenter(
override fun onFailure(call: Call<Fact>, t: Throwable) {
CrashMonitor.trackWarning()
}
})
})*/
}

fun attachView(catsView: ICatsView) {
Expand All @@ -32,4 +57,9 @@ class CatsPresenter(
fun detachView() {
_catsView = null
}

fun onStop()
{
job?.cancel()
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/otus/homework/coroutines/CatsService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import retrofit2.http.GET
interface CatsService {

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

class CatsView @JvmOverloads constructor(
context: Context,
Expand All @@ -13,20 +15,27 @@ class CatsView @JvmOverloads constructor(
) : ConstraintLayout(context, attrs, defStyleAttr), ICatsView {

var presenter :CatsPresenter? = null
var viewModel: CatsViewModel? = null

override fun onFinishInflate() {
super.onFinishInflate()
findViewById<Button>(R.id.button).setOnClickListener {
presenter?.onInitComplete()
if (presenter != null) {
presenter?.onInitComplete()
} else {
viewModel?.onInitComplete()
}
}
}

override fun populate(fact: Fact) {
findViewById<TextView>(R.id.fact_textView).text = fact.fact
override fun populate(model: CatModel) {
findViewById<TextView>(R.id.fact_textView).text = model.text
val imageview = findViewById<ImageView>(R.id.imageView)
Picasso.get().load(model.imageUrl).into(imageview)
}
}

interface ICatsView {

fun populate(fact: Fact)
fun populate(model: CatModel)
}
45 changes: 45 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,45 @@
package otus.homework.coroutines

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.net.SocketTimeoutException

class CatsViewModel (
private val catsService: CatsService,
private val imagesService: ImageService,
private val onShowToast: (String?) -> Unit
) : ViewModel() {

private val _state = MutableStateFlow<Result>(Result.Error("Произошла ошибка при получении факта"))
val state: StateFlow<Result> = _state.asStateFlow()

private val handler = CoroutineExceptionHandler { _, exception ->
if(exception is SocketTimeoutException){
onShowToast("Не удалось получить ответ от сервера")
}else {
CrashMonitor.trackWarning("CoroutineExceptionHandler got $exception")
onShowToast(exception.message)
}
_state.value = Result.Error(exception.message)
}

fun onInitComplete() {
viewModelScope.launch (handler){
val fact = async { catsService.getCatFact() }
val image = async { imagesService.getCatImage().firstOrNull() }
val model = CatModel(fact.await().fact, image.await()?.url)
_state.value = Result.Success(model)
}
}
}

sealed class Result {
data class Success<T>(val data: T) : Result()
data class Error(val msg: String?) : Result()
}
2 changes: 1 addition & 1 deletion app/src/main/java/otus/homework/coroutines/CrashMonitor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ object CrashMonitor {
/**
* Pretend this is Crashlytics/AppCenter
*/
fun trackWarning() {
fun trackWarning(message: String?) {
}
}
10 changes: 10 additions & 0 deletions app/src/main/java/otus/homework/coroutines/DiContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package otus.homework.coroutines

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import kotlin.getValue

class DiContainer {

Expand All @@ -12,5 +13,14 @@ class DiContainer {
.build()
}

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

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

val imageService by lazy {imageRetrofit.create(ImageService::class.java)}
}
9 changes: 9 additions & 0 deletions app/src/main/java/otus/homework/coroutines/ImageService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package otus.homework.coroutines

import retrofit2.http.GET

interface ImageService {

@GET("v1/images/search")
suspend fun getCatImage() : List<CatImage>
}
49 changes: 43 additions & 6 deletions app/src/main/java/otus/homework/coroutines/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package otus.homework.coroutines

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {

lateinit var catsPresenter: CatsPresenter

private val diContainer = DiContainer()

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -15,16 +17,51 @@ 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()

// catsPresenter = CatsPresenter(
// diContainer.service,
// diContainer.imageService,
// ::onShowToast
// )
// view.presenter = catsPresenter
// catsPresenter.attachView(view)
// catsPresenter.onInitComplete()

val viewModel = CatsViewModel(
diContainer.service,
diContainer.imageService,
::onShowToast
)
viewModel.onInitComplete()
view.viewModel = viewModel

lifecycleScope.launch {
viewModel.state.collect { state ->
when (state) {
is Result.Success<*> -> {
if (state.data is CatModel)
view.populate(state.data)
}

is Result.Error -> {
onShowToast(state.msg)
}
}
}
}
}

override fun onStop() {
if (isFinishing) {
catsPresenter.detachView()
if(::catsPresenter.isInitialized) {
catsPresenter.detachView()
catsPresenter.onStop()
}
}
super.onStop()
}

private fun onShowToast(message: String?) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
15 changes: 12 additions & 3 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,24 @@
android:layout_height="match_parent"
tools:context=".MainActivity">

<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/fact_textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/fact_textView"
android:textColor="@color/black"
android:textSize="24sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@+id/imageView" />

<Button
android:id="@+id/button"
Expand All @@ -25,6 +33,7 @@
android:text="@string/more_facts"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fact_textView" />
app:layout_constraintTop_toBottomOf="@+id/fact_textView"
app:layout_constraintBottom_toBottomOf="parent"/>

</otus.homework.coroutines.CatsView>