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.2.0"
}
48 changes: 32 additions & 16 deletions app/src/main/java/otus/homework/coroutines/CatsPresenter.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
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.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import otus.homework.coroutines.CrashMonitor.trackWarning
import otus.homework.coroutines.model.Model
import java.net.SocketTimeoutException


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

private var _catsView: ICatsView? = null

private val presenterScope = CoroutineScope(Dispatchers.Main + CoroutineName("CatsCoroutine"))
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()!!)
}
presenterScope.launch {
try {
val fact = async { catsService.getCatFact() }
val image = async { imagesService.getImage().firstOrNull() }

_catsView?.populate(Model(fact.await(), image.await()?.url))
} catch (e: SocketTimeoutException) {
println("Caught $e")
onShowToast("Не удалось получить ответ от сервера")

} catch (e: Exception) {
trackWarning(e.message)
onShowToast(e.message)
}

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

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

fun onStop() {
presenterScope.cancel()
}
}
3 changes: 2 additions & 1 deletion app/src/main/java/otus/homework/coroutines/CatsService.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package otus.homework.coroutines

import otus.homework.coroutines.model.Fact
import retrofit2.Call
import retrofit2.http.GET

interface CatsService {

@GET("fact")
fun getCatFact() : Call<Fact>
suspend fun getCatFact(): Fact
}
22 changes: 17 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,30 +3,42 @@ 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
import otus.homework.coroutines.model.Fact
import otus.homework.coroutines.model.Image
import otus.homework.coroutines.model.Model

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
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: Model) {
findViewById<TextView>(R.id.fact_textView).text = model.fact.fact
val Imageview = findViewById<ImageView>(R.id.imageView)
Picasso.get().load(model.imageUrl).into(Imageview)
}
}

interface ICatsView {

fun populate(fact: Fact)
fun populate(model: Model)
}
49 changes: 49 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,49 @@
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 otus.homework.coroutines.CrashMonitor.trackWarning
import otus.homework.coroutines.model.Model
import java.net.SocketTimeoutException

class CatsViewModel(
private val catsService: CatsService,
private val imagesService: ImagesService,
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 {
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.getImage().firstOrNull() }
val model = Model(fact.await(), 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()
}
3 changes: 2 additions & 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,7 @@ object CrashMonitor {
/**
* Pretend this is Crashlytics/AppCenter
*/
fun trackWarning() {
fun trackWarning(message: String?) {
println("CrashMonitor: $message")
}
}
9 changes: 9 additions & 0 deletions app/src/main/java/otus/homework/coroutines/DiContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,14 @@ class DiContainer {
.build()
}

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

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

val imagesService by lazy { imagesRetrofit.create(ImagesService::class.java) }
}
10 changes: 0 additions & 10 deletions app/src/main/java/otus/homework/coroutines/Fact.kt

This file was deleted.

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

import otus.homework.coroutines.model.Image
import retrofit2.http.GET

interface ImagesService {

@GET("v1/images/search")
suspend fun getImage(): List<Image>
}
52 changes: 46 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,29 +2,69 @@ 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
import otus.homework.coroutines.model.Model

class MainActivity : AppCompatActivity() {

lateinit var catsPresenter: CatsPresenter

private val diContainer = DiContainer()
private var isPresenterMode = true

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val view = layoutInflater.inflate(R.layout.activity_main, null) as CatsView
setContentView(view)
if (isPresenterMode) {
catsPresenter = CatsPresenter(
diContainer.service,
diContainer.imagesService,
::onShowToast
)

view.presenter = catsPresenter
catsPresenter.attachView(view)
catsPresenter.onInitComplete()
} else {
val viewModel = CatsViewModel(
diContainer.service,
diContainer.imagesService,
::onShowToast
)
viewModel.onInitComplete()
lifecycleScope.launch {
viewModel.state.collect { state ->
when (state) {
is Result.Success<*> -> {
if (state.data is Model) {
view.populate(state.data)
}
}

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

catsPresenter = CatsPresenter(diContainer.service)
view.presenter = catsPresenter
catsPresenter.attachView(view)
catsPresenter.onInitComplete()
private fun onShowToast(message: String?) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}

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

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

import com.google.gson.annotations.SerializedName

data class Fact(
@field:SerializedName("fact")
val fact: String,
@field:SerializedName("length")
val length: Int
)
15 changes: 15 additions & 0 deletions app/src/main/java/otus/homework/coroutines/model/Image.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package otus.homework.coroutines.model

import com.google.gson.annotations.SerializedName

data class Image(
@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/model/Model.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package otus.homework.coroutines.model

data class Model(
val fact: Fact,
val imageUrl: String?
)
14 changes: 11 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,23 @@
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_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 +32,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>