From b797c049c9a5c8df45941601bd6d1ef6830f0f4c Mon Sep 17 00:00:00 2001 From: maxdgladkov Date: Wed, 8 Jul 2026 17:08:34 +0300 Subject: [PATCH 1/6] timer coroutines impl --- .../ui/timer/TimerFragment.kt | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 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 1b7c0f1..de1f831 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 @@ -6,9 +6,9 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.FragmentTimerBinding import java.util.Locale @@ -34,6 +34,7 @@ class TimerFragment : Fragment() { } } + private var job: Job? = null private fun setButtonsState(started: Boolean) { with(binding) { btnStart.isEnabled = !started @@ -75,11 +76,26 @@ class TimerFragment : Fragment() { } private fun startTimer() { - // TODO: Start timer + resetTimer() + + job = viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { + while (started) { + time += 100.milliseconds + delay(100.milliseconds) + } + } } private fun stopTimer() { - // TODO: Stop timer + job?.cancel() + } + + private fun resetTimer() { + if (job != null) { + job?.cancel() + } + + time = 0.milliseconds } override fun onDestroyView() { From fc1bf31356d0884f7e9fc8998204e146d816c14f Mon Sep 17 00:00:00 2001 From: maxdgladkov Date: Wed, 8 Jul 2026 17:41:45 +0300 Subject: [PATCH 2/6] timer kotlinFlow impl --- .../ui/timer/TimerFragment.kt | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 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 de1f831..b83afb5 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,10 +5,13 @@ 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.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.FragmentTimerBinding import java.util.Locale @@ -18,12 +21,11 @@ import kotlin.time.Duration.Companion.milliseconds class TimerFragment : Fragment() { + private val timerIncrement = 10.milliseconds private var _binding: FragmentTimerBinding? = null private val binding get() = _binding!! - private var time: Duration by Delegates.observable(Duration.ZERO) { _, _, newValue -> - binding.time.text = newValue.toDisplayString() - } + private var timeFlow = MutableStateFlow(0.milliseconds) private var started by Delegates.observable(false) { _, _, newValue -> setButtonsState(newValue) @@ -54,12 +56,21 @@ class TimerFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) savedInstanceState?.let { - time = it.getLong(TIME).milliseconds + val initialTime = it.getLong(TIME).milliseconds + timeFlow = MutableStateFlow(initialTime) started = it.getBoolean(STARTED) } setButtonsState(started) + with(binding) { - time.text = this@TimerFragment.time.toDisplayString() + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { + repeatOnLifecycle(Lifecycle.State.STARTED) { + timeFlow.collect { + time.text = it.toDisplayString() + } + } + } + btnStart.setOnClickListener { started = true } @@ -71,7 +82,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) } @@ -80,8 +91,8 @@ class TimerFragment : Fragment() { job = viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { while (started) { - time += 100.milliseconds - delay(100.milliseconds) + timeFlow.emit(timeFlow.value + timerIncrement) + delay(timerIncrement) } } } @@ -95,7 +106,7 @@ class TimerFragment : Fragment() { job?.cancel() } - time = 0.milliseconds + timeFlow.value = 0.milliseconds } override fun onDestroyView() { From aa79bfbf73d7602b5e9c4fffb7473d298f7d857c Mon Sep 17 00:00:00 2001 From: maxdgladkov Date: Wed, 8 Jul 2026 17:42:25 +0300 Subject: [PATCH 3/6] timer toDisplayString() fix --- .../ui/timer/TimerFragment.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 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 b83afb5..ecda29d 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 @@ -118,12 +118,16 @@ class TimerFragment : Fragment() { private const val TIME = "time" private const val STARTED = "started" - private fun Duration.toDisplayString(): String = String.format( - Locale.getDefault(), - "%02d:%02d.%03d", - this.inWholeMinutes.toInt(), - this.inWholeSeconds.toInt(), - this.inWholeMilliseconds.toInt() - ) + private fun Duration.toDisplayString(): String { + val minutes = this.inWholeMinutes.toInt() + val seconds = (this.inWholeSeconds % 60).toInt() + val millis = (this.inWholeMilliseconds % 1000).toInt() + + return String.format( + Locale.getDefault(), + "%02d:%02d.%03d", + minutes, seconds, millis + ) + } } } \ No newline at end of file From 350eba930936ef63ef2c9ea596b2603c09bfc0c3 Mon Sep 17 00:00:00 2001 From: maxdgladkov Date: Sat, 11 Jul 2026 21:23:21 +0300 Subject: [PATCH 4/6] login coroutines --- .../ui/login/LoginViewModel.kt | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) 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..2eefb84 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,6 +3,11 @@ 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 kotlinx.coroutines.withContext +import ru.otus.coroutineshomework.ui.login.data.Credentials class LoginViewModel : ViewModel() { @@ -15,13 +20,34 @@ class LoginViewModel : ViewModel() { * @param password user password */ fun login(name: String, password: String) { - // TODO: Implement login + _state.value = LoginViewState.LoggingIn + + viewModelScope.launch(Dispatchers.IO) { + try { + val user = LoginApi().login(Credentials(name, password)) + withContext(Dispatchers.Main) { + _state.value = LoginViewState.Content(user) + } + + } catch (e: Exception) { + withContext(Dispatchers.Main) { + _state.value = LoginViewState.Login(e) + } + } + } } /** * Logout from the network */ fun logout() { - // TODO: Implement logout + _state.value = LoginViewState.LoggingOut + + viewModelScope.launch(Dispatchers.IO) { + LoginApi().logout() + withContext(Dispatchers.Main) { + _state.value = LoginViewState.Login(null) + } + } } } From 672b99eafb8e71be7aad1f0883600aa53df3d6bc Mon Sep 17 00:00:00 2001 From: maxdgladkov Date: Wed, 15 Jul 2026 17:05:31 +0300 Subject: [PATCH 5/6] login flow impl --- .../ui/login/LoginFragment.kt | 16 +++-- .../ui/login/LoginViewModel.kt | 59 +++++++++++-------- 2 files changed, 44 insertions(+), 31 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..4ff5b36 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,8 @@ 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.launch import ru.otus.coroutineshomework.databinding.ContentBinding import ru.otus.coroutineshomework.databinding.FragmentLoginBinding import ru.otus.coroutineshomework.databinding.LoadingBinding @@ -43,12 +45,14 @@ class LoginFragment : Fragment() { setupLogin() setupContent() - loginViewModel.state.observe(viewLifecycleOwner) { - when(it) { - is LoginViewState.Login -> showLogin(it) - LoginViewState.LoggingIn -> showLoggingIn() - is LoginViewState.Content -> showContent(it) - LoginViewState.LoggingOut -> showLoggingOut() + viewLifecycleOwner.lifecycleScope.launch { + loginViewModel.state.collect { + when(it) { + is LoginViewState.Login -> showLogin(it) + LoginViewState.LoggingIn -> showLoggingIn() + is LoginViewState.Content -> showContent(it) + LoginViewState.LoggingOut -> showLoggingOut() + } } } } 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 2eefb84..e1fe468 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 @@ -1,18 +1,34 @@ 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.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import ru.otus.coroutineshomework.ui.login.data.Credentials class LoginViewModel : ViewModel() { - private val _state = MutableLiveData(LoginViewState.Login()) - val state: LiveData = _state + private val stateFlow = MutableStateFlow(LoginViewState.Login()) + val state: StateFlow = stateFlow.asStateFlow() + + private fun loginFlow(name: String, password: String): Flow = flow { + emit(LoginViewState.LoggingIn) + + val user = withContext(Dispatchers.IO) { + LoginApi().login(Credentials(name, password)) + } + + emit(LoginViewState.Content(user)) + }.catch { e -> + emit(LoginViewState.Login(e as Exception?)) + } /** * Login to the network @@ -20,19 +36,9 @@ class LoginViewModel : ViewModel() { * @param password user password */ fun login(name: String, password: String) { - _state.value = LoginViewState.LoggingIn - - viewModelScope.launch(Dispatchers.IO) { - try { - val user = LoginApi().login(Credentials(name, password)) - withContext(Dispatchers.Main) { - _state.value = LoginViewState.Content(user) - } - - } catch (e: Exception) { - withContext(Dispatchers.Main) { - _state.value = LoginViewState.Login(e) - } + viewModelScope.launch { + loginFlow(name,password).collect { state -> + stateFlow.value = state } } } @@ -40,14 +46,17 @@ class LoginViewModel : ViewModel() { /** * Logout from the network */ - fun logout() { - _state.value = LoginViewState.LoggingOut - - viewModelScope.launch(Dispatchers.IO) { - LoginApi().logout() - withContext(Dispatchers.Main) { - _state.value = LoginViewState.Login(null) - } + private fun logoutFlow(): Flow = flow { + emit( LoginViewState.LoggingOut) + withContext(Dispatchers.IO) { + LoginApi().logout() } + emit(LoginViewState.Login(null)) } + fun logout() = viewModelScope.launch { + logoutFlow().collect { state -> + stateFlow.value = state + } + } + } From 5ae907a22810e4ac59840dce348a4889e5b8c537 Mon Sep 17 00:00:00 2001 From: maxdgladkov Date: Thu, 16 Jul 2026 15:30:16 +0300 Subject: [PATCH 6/6] parallel coroutine network calls --- .../ui/network/NetworkViewModel.kt | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) 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..d3af33c 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 @@ -17,8 +21,26 @@ class NetworkViewModel : ViewModel() { private val _result: MutableLiveData = MutableLiveData() val result: LiveData = _result - fun startTest(numberOfThreads: Int) { - // TODO: Implement the logic + fun startTest(numberOfThreads: Int) = viewModelScope.launch { + _running.value = true + + val results = List(numberOfThreads) { + async { emulateBlockingNetworkRequest() } + }.awaitAll() + + val sum = results.filter { + it.isSuccess + }.mapNotNull { + it.getOrNull() + }.sum() + + if (sum == 0.toLong()) { + _result.value = 0.toLong() + } else { + _result.value = sum / numberOfThreads + } + + _running.value = false } private companion object {