From eeccb9f0b537af82feb475afff3cb71c1771914f Mon Sep 17 00:00:00 2001 From: Adel Khaziakhmetov Date: Wed, 4 Jun 2025 12:00:23 +0300 Subject: [PATCH 1/6] added coroutine timer --- .../ui/timer/TimerFragment.kt | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 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..116b092 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,21 +5,26 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.CoroutineScope +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.time.LocalDateTime import java.util.Locale import kotlin.properties.Delegates import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.DurationUnit +import kotlin.time.toDuration 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() @@ -75,11 +80,21 @@ class TimerFragment : Fragment() { } private fun startTimer() { - // TODO: Start timer + val diff = 1L.toDuration(DurationUnit.MILLISECONDS) + + timerJob = CoroutineScope(Dispatchers.Default).launch { + while (isActive) { + CoroutineScope(Dispatchers.Main).launch { + time = time.plus(diff) + } + + delay(1L) + } + } } private fun stopTimer() { - // TODO: Stop timer + timerJob?.cancel() } override fun onDestroyView() { @@ -95,8 +110,8 @@ class TimerFragment : Fragment() { Locale.getDefault(), "%02d:%02d.%03d", this.inWholeMinutes.toInt(), - this.inWholeSeconds.toInt(), - this.inWholeMilliseconds.toInt() + this.inWholeSeconds.toInt() % 60, + this.inWholeMilliseconds.toInt() % 1000 ) } } \ No newline at end of file From 57e4140731d9977d9a469f80796aba3ef6d9a049 Mon Sep 17 00:00:00 2001 From: Adel Khaziakhmetov Date: Wed, 4 Jun 2025 12:12:27 +0300 Subject: [PATCH 2/6] added flow timer --- .../ui/timer/TimerFragment.kt | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 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 116b092..b22081a 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 @@ -8,7 +8,11 @@ import androidx.fragment.app.Fragment import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.FragmentTimerBinding @@ -24,10 +28,15 @@ 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 timerJob: Job? = null + private var time: Duration = Duration.ZERO + private val timeFlow: Flow = flow { + while (currentCoroutineContext().isActive) { + time = time.plus(1L.toDuration(DurationUnit.MILLISECONDS)) + emit(time) + delay(1) + } } private var started by Delegates.observable(false) { _, _, newValue -> @@ -63,7 +72,7 @@ class TimerFragment : Fragment() { } setButtonsState(started) with(binding) { - time.text = this@TimerFragment.time.toDisplayString() + time.text = this.time.toDisplayString() btnStart.setOnClickListener { started = true } @@ -80,16 +89,12 @@ class TimerFragment : Fragment() { } private fun startTimer() { - val diff = 1L.toDuration(DurationUnit.MILLISECONDS) - timerJob = CoroutineScope(Dispatchers.Default).launch { - while (isActive) { + timeFlow.onEach { CoroutineScope(Dispatchers.Main).launch { - time = time.plus(diff) + binding.time.text = it.toDisplayString() } - - delay(1L) - } + }.collect { } } } From 91494d1e7cb11f57eeb775ee07956d32bef29d1a Mon Sep 17 00:00:00 2001 From: Adel Khaziakhmetov Date: Wed, 4 Jun 2025 12:52:30 +0300 Subject: [PATCH 3/6] added flow timer --- .../ui/timer/TimerFragment.kt | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 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 b22081a..4d943cf 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,14 +5,20 @@ 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.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.runningFold import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.FragmentTimerBinding @@ -30,14 +36,7 @@ class TimerFragment : Fragment() { private val binding get() = _binding!! private var timerJob: Job? = null - private var time: Duration = Duration.ZERO - private val timeFlow: Flow = flow { - while (currentCoroutineContext().isActive) { - time = time.plus(1L.toDuration(DurationUnit.MILLISECONDS)) - emit(time) - delay(1) - } - } + private var timeFlow: MutableStateFlow = MutableStateFlow(Duration.ZERO) private var started by Delegates.observable(false) { _, _, newValue -> setButtonsState(newValue) @@ -66,13 +65,23 @@ class TimerFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + timeFlow.collect { + binding.time.text = it.toDisplayString() + } + } + } + savedInstanceState?.let { - time = it.getLong(TIME).milliseconds + timeFlow = MutableStateFlow(it.getLong(TIME).milliseconds) started = it.getBoolean(STARTED) } + setButtonsState(started) with(binding) { - time.text = this.time.toDisplayString() + time.text = this@TimerFragment.timeFlow.value.toDisplayString() btnStart.setOnClickListener { started = true } @@ -84,17 +93,16 @@ 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) } private fun startTimer() { timerJob = CoroutineScope(Dispatchers.Default).launch { - timeFlow.onEach { - CoroutineScope(Dispatchers.Main).launch { - binding.time.text = it.toDisplayString() - } - }.collect { } + while (isActive) { + timeFlow.emit(timeFlow.value + 1L.milliseconds) + delay(1L) + } } } From 4c1154ad3b23b5df476e0892eec90330dbfaed3b Mon Sep 17 00:00:00 2001 From: Adel Khaziakhmetov Date: Wed, 4 Jun 2025 13:43:47 +0300 Subject: [PATCH 4/6] login with coroutine --- .../ui/login/LoginViewModel.kt | 36 +++++++++++++++++-- 1 file changed, 34 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..4638373 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,12 @@ 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.CoroutineScope +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 +21,39 @@ class LoginViewModel : ViewModel() { * @param password user password */ fun login(name: String, password: String) { - // TODO: Implement login + viewModelScope.launch { + withContext(Dispatchers.Default) { + updateState(LoginViewState.LoggingIn) + + try { + val user = LoginApi().login(Credentials(name, password)) + + updateState(LoginViewState.Content(user)) + } catch (ex: Exception) { + updateState(LoginViewState.Login(ex)) + } + } + } } /** * Logout from the network */ fun logout() { - // TODO: Implement logout + viewModelScope.launch { + withContext(Dispatchers.Default) { + updateState(LoginViewState.LoggingOut) + + LoginApi().logout() + + updateState(LoginViewState.Login()) + } + } + } + + private suspend fun updateState(state: LoginViewState) { + withContext(Dispatchers.Main) { + _state.value = state + } } } From d1199d671cd6a220af04e1f159bf312fd7ccf60f Mon Sep 17 00:00:00 2001 From: Adel Khaziakhmetov Date: Wed, 4 Jun 2025 13:56:21 +0300 Subject: [PATCH 5/6] login with flow --- .../ui/login/LoginFragment.kt | 17 +++++--- .../ui/login/LoginViewModel.kt | 41 ++++++++++--------- 2 files changed, 33 insertions(+), 25 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..819b75a 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.collect +import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.ContentBinding import ru.otus.coroutineshomework.databinding.FragmentLoginBinding import ru.otus.coroutineshomework.databinding.LoadingBinding @@ -43,12 +46,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() + 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 4638373..7930818 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 @@ -6,39 +6,30 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineScope 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.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() - /** - * Login to the network - * @param name user name - * @param password user password - */ fun login(name: String, password: String) { viewModelScope.launch { withContext(Dispatchers.Default) { - updateState(LoginViewState.LoggingIn) - - try { - val user = LoginApi().login(Credentials(name, password)) - - updateState(LoginViewState.Content(user)) - } catch (ex: Exception) { - updateState(LoginViewState.Login(ex)) + loginFlow(name, password).collect { + updateState(it) } } } } - /** - * Logout from the network - */ fun logout() { viewModelScope.launch { withContext(Dispatchers.Default) { @@ -51,9 +42,21 @@ class LoginViewModel : ViewModel() { } } + private fun loginFlow(name: String, password: String): Flow = flow { + emit(LoginViewState.LoggingIn) + + try { + val user = LoginApi().login(Credentials(name, password)) + + emit(LoginViewState.Content(user)) + } catch (ex: Exception) { + emit(LoginViewState.Login(ex)) + } + } + private suspend fun updateState(state: LoginViewState) { withContext(Dispatchers.Main) { - _state.value = state + stateFlow.value = state } } } From ee4169c8fbf75ff4757847d9a1bea3a90796e3e7 Mon Sep 17 00:00:00 2001 From: Adel Khaziakhmetov Date: Wed, 4 Jun 2025 14:14:23 +0300 Subject: [PATCH 6/6] calculating function time --- .../ui/network/NetworkViewModel.kt | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) 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..91f2059 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 @@ -18,7 +22,22 @@ class NetworkViewModel : ViewModel() { val result: LiveData = _result fun startTest(numberOfThreads: Int) { - // TODO: Implement the logic + viewModelScope.launch { + _running.value = true + + val results = (1..numberOfThreads).map { + async { emulateBlockingNetworkRequest().getOrNull() } + }.awaitAll().filterNotNull() + + _result.value = calculateAverageTime(results) + _running.value = false + } + } + + private fun calculateAverageTime(list: List): Long { + if (list.isEmpty()) return 0 + + return list.sum() / list.count() } private companion object {