From 3a3ad701ee6ab5df0ad754e1a837e3a137a01f95 Mon Sep 17 00:00:00 2001 From: Dmitriy Bulygin Date: Sun, 30 Nov 2025 12:10:26 +0300 Subject: [PATCH 1/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=201.1=20-=20=D0=A2=D0=B0=D0=B9=D0=BC=D0=B5=D1=80=20=D1=81=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=D0=BC=20coroutines?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otus/coroutineshomework/ui/timer/TimerFragment.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/timer/TimerFragment.kt b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/timer/TimerFragment.kt index 1b7c0f1..8cbf468 100644 --- a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/timer/TimerFragment.kt +++ b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/timer/TimerFragment.kt @@ -14,12 +14,15 @@ import ru.otus.coroutineshomework.databinding.FragmentTimerBinding import java.util.Locale import kotlin.properties.Delegates import kotlin.time.Duration +import kotlin.time.Duration.Companion.microseconds import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.nanoseconds class TimerFragment : Fragment() { private var _binding: FragmentTimerBinding? = null private val binding get() = _binding!! + private var timerJob: Job? = null private var time: Duration by Delegates.observable(Duration.ZERO) { _, _, newValue -> binding.time.text = newValue.toDisplayString() @@ -76,10 +79,17 @@ class TimerFragment : Fragment() { private fun startTimer() { // TODO: Start timer + timerJob = lifecycleScope.launch { + while (isActive) { + delay(100) + time += 100.milliseconds + } + } } private fun stopTimer() { // TODO: Stop timer + timerJob?.cancel() } override fun onDestroyView() { From d05715a74e646dbaa6ff6b27d716f73b62daecef Mon Sep 17 00:00:00 2001 From: Dmitriy Bulygin Date: Sun, 30 Nov 2025 12:55:00 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=201.2=20-=20=D0=9F=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4=20?= =?UTF-8?q?=D1=82=D0=B0=D0=B9=D0=BC=D0=B5=D1=80=D0=B0=20=D0=BD=D0=B0=20Kot?= =?UTF-8?q?lin=20Flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/timer/TimerFragment.kt | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/timer/TimerFragment.kt b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/timer/TimerFragment.kt index 8cbf468..3040ae6 100644 --- a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/timer/TimerFragment.kt +++ b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/timer/TimerFragment.kt @@ -5,28 +5,26 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.FragmentTimerBinding import java.util.Locale import kotlin.properties.Delegates import kotlin.time.Duration -import kotlin.time.Duration.Companion.microseconds import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.nanoseconds class TimerFragment : Fragment() { private var _binding: FragmentTimerBinding? = null private val binding get() = _binding!! private var timerJob: Job? = null - - private var time: Duration by Delegates.observable(Duration.ZERO) { _, _, newValue -> - binding.time.text = newValue.toDisplayString() - } + private var timeFlow: MutableStateFlow = MutableStateFlow(0.milliseconds) private var started by Delegates.observable(false) { _, _, newValue -> setButtonsState(newValue) @@ -56,12 +54,20 @@ class TimerFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) savedInstanceState?.let { - time = it.getLong(TIME).milliseconds + timeFlow = MutableStateFlow(it.getLong(TIME).milliseconds) started = it.getBoolean(STARTED) } + + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + timeFlow.collect { duration -> + binding.time.text = duration.toDisplayString() + } + } + } + setButtonsState(started) with(binding) { - time.text = this@TimerFragment.time.toDisplayString() btnStart.setOnClickListener { started = true } @@ -73,7 +79,7 @@ class TimerFragment : Fragment() { override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putLong(TIME, time.inWholeMilliseconds) + outState.putLong(TIME, timeFlow.value.inWholeMilliseconds) outState.putBoolean(STARTED, started) } @@ -82,7 +88,7 @@ class TimerFragment : Fragment() { timerJob = lifecycleScope.launch { while (isActive) { delay(100) - time += 100.milliseconds + timeFlow.emit(timeFlow.value + 100.milliseconds) } } } From f6303824e5b9ecb7b7afb7f576df10080332818e Mon Sep 17 00:00:00 2001 From: Dmitriy Bulygin Date: Sun, 30 Nov 2025 13:18:34 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=202.1=20-=20Login?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/LoginViewModel.kt | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginViewModel.kt b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginViewModel.kt index 5fae38a..068a792 100644 --- a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginViewModel.kt +++ b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginViewModel.kt @@ -3,9 +3,13 @@ package ru.otus.coroutineshomework.ui.login import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import ru.otus.coroutineshomework.ui.login.data.Credentials class LoginViewModel : ViewModel() { - + private val api = LoginApi() private val _state = MutableLiveData(LoginViewState.Login()) val state: LiveData = _state @@ -16,6 +20,15 @@ class LoginViewModel : ViewModel() { */ fun login(name: String, password: String) { // TODO: Implement login + _state.value = LoginViewState.LoggingIn + viewModelScope.launch(Dispatchers.IO) { + try { + val user = api.login(Credentials(name, password)) + _state.postValue(LoginViewState.Content(user)) + } catch (e: Exception) { + _state.postValue(LoginViewState.Login(e)) + } + } } /** @@ -23,5 +36,10 @@ class LoginViewModel : ViewModel() { */ fun logout() { // TODO: Implement logout + _state.value = LoginViewState.LoggingOut + viewModelScope.launch(Dispatchers.IO) { + api.logout() + _state.postValue(LoginViewState.Login()) + } } } From bb5cc13b6df2bc6f5b37819b2fd33f445781b1f9 Mon Sep 17 00:00:00 2001 From: Dmitriy Bulygin Date: Sun, 30 Nov 2025 13:41:46 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=202.2=20-=20Login=20=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20Kotlin?= =?UTF-8?q?=20Flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/LoginFragment.kt | 13 ++++-- .../ui/login/LoginViewModel.kt | 46 ++++++++++++------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginFragment.kt b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginFragment.kt index 06c3afe..d6c870c 100644 --- a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginFragment.kt +++ b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginFragment.kt @@ -7,6 +7,9 @@ import android.view.ViewGroup import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import ru.otus.coroutineshomework.databinding.ContentBinding import ru.otus.coroutineshomework.databinding.FragmentLoginBinding import ru.otus.coroutineshomework.databinding.LoadingBinding @@ -43,14 +46,14 @@ class LoginFragment : Fragment() { setupLogin() setupContent() - loginViewModel.state.observe(viewLifecycleOwner) { - when(it) { - is LoginViewState.Login -> showLogin(it) + loginViewModel.state.onEach { state -> + when(state) { + is LoginViewState.Login -> showLogin(state) LoginViewState.LoggingIn -> showLoggingIn() - is LoginViewState.Content -> showContent(it) + is LoginViewState.Content -> showContent(state) LoginViewState.LoggingOut -> showLoggingOut() } - } + }.launchIn(viewLifecycleOwner.lifecycleScope) } private fun setupLogin() { diff --git a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginViewModel.kt b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginViewModel.kt index 068a792..0c6a58d 100644 --- a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginViewModel.kt +++ b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginViewModel.kt @@ -5,13 +5,21 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import ru.otus.coroutineshomework.ui.login.data.Credentials class LoginViewModel : ViewModel() { private val api = LoginApi() - private val _state = MutableLiveData(LoginViewState.Login()) - val state: LiveData = _state + private val _state = MutableStateFlow(LoginViewState.Login()) + val state: StateFlow = _state /** * Login to the network @@ -20,15 +28,9 @@ class LoginViewModel : ViewModel() { */ fun login(name: String, password: String) { // TODO: Implement login - _state.value = LoginViewState.LoggingIn - viewModelScope.launch(Dispatchers.IO) { - try { - val user = api.login(Credentials(name, password)) - _state.postValue(LoginViewState.Content(user)) - } catch (e: Exception) { - _state.postValue(LoginViewState.Login(e)) - } - } + loginFlow(name, password) + .onEach { _state.value = it} + .launchIn(viewModelScope) } /** @@ -36,10 +38,22 @@ class LoginViewModel : ViewModel() { */ fun logout() { // TODO: Implement logout - _state.value = LoginViewState.LoggingOut - viewModelScope.launch(Dispatchers.IO) { - api.logout() - _state.postValue(LoginViewState.Login()) - } + logoutFlow() + .onEach { _state.value = it} + .launchIn(viewModelScope) } + + private fun loginFlow(name: String, password: String): Flow = flow { + emit(LoginViewState.LoggingIn) + val user = api.login(Credentials(name, password)) + emit(LoginViewState.Content(user)) + }.catch { e -> + emit(LoginViewState.Login(e as Exception?)) + }.flowOn(Dispatchers.IO) + + private fun logoutFlow(): Flow = flow { + emit(LoginViewState.LoggingOut) + api.logout() + emit(LoginViewState.Login()) + }.flowOn(Dispatchers.IO) } From edee09ba30cdb4a451788148559450803aa1b905 Mon Sep 17 00:00:00 2001 From: Dmitriy Bulygin Date: Sun, 30 Nov 2025 16:04:42 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=203.=20Speed-test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/network/NetworkViewModel.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/network/NetworkViewModel.kt b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/network/NetworkViewModel.kt index f006e03..e2dd22a 100644 --- a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/network/NetworkViewModel.kt +++ b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/network/NetworkViewModel.kt @@ -4,8 +4,12 @@ import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.random.Random @@ -19,6 +23,24 @@ class NetworkViewModel : ViewModel() { fun startTest(numberOfThreads: Int) { // TODO: Implement the logic + viewModelScope.launch { + _running.value = true + _result.value = null + + val requests = (1..numberOfThreads).map { + async { emulateBlockingNetworkRequest() } + } + val results = requests.awaitAll() + val successResults = results.filter { it.isSuccess } + + _result.value = if (successResults.isNotEmpty()) { + successResults.map { it.getOrNull() ?:0}.average().toLong() + } else { + null + } + + _running.value = false + } } private companion object {