From c3cb4c2ecfd51aacc7818c5eadf551550b535b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D1=8C=D0=B1=D0=B5=D1=80=D1=82?= Date: Sun, 16 Mar 2025 18:40:12 +0500 Subject: [PATCH 1/5] Task 1.1 coroutines timer --- .../ui/timer/TimerFragment.kt | 20 ++++++++++++++++--- 1 file changed, 17 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..e5c73e8 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,12 +5,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.coroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.FragmentTimerBinding +import java.util.Calendar import java.util.Locale import kotlin.properties.Delegates import kotlin.time.Duration @@ -21,6 +23,8 @@ 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 +79,19 @@ class TimerFragment : Fragment() { } private fun startTimer() { - // TODO: Start timer + stopTimer() + timerJob = lifecycle.coroutineScope.launch { + val prevTime = System.currentTimeMillis() + while(isActive){ + val curTime = System.currentTimeMillis() + time = (curTime-prevTime).milliseconds + delay(DELAY) + } + } } private fun stopTimer() { - // TODO: Stop timer + timerJob?.cancel() } override fun onDestroyView() { @@ -91,6 +103,8 @@ class TimerFragment : Fragment() { private const val TIME = "time" private const val STARTED = "started" + private const val DELAY:Long = 50 + private fun Duration.toDisplayString(): String = String.format( Locale.getDefault(), "%02d:%02d.%03d", From 5bfff82782c80f1bf13934e5fe8aa48c3484b03d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D1=8C=D0=B1=D0=B5=D1=80=D1=82?= Date: Sun, 16 Mar 2025 19:17:07 +0500 Subject: [PATCH 2/5] Task 1.2 flow timer --- .../ui/timer/TimerFragment.kt | 24 +++++++++++++------ 1 file changed, 17 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 e5c73e8..aa31d91 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 @@ -4,11 +4,16 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.collection.emptyLongSet import androidx.fragment.app.Fragment import androidx.lifecycle.coroutineScope +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.FragmentTimerBinding @@ -16,6 +21,7 @@ import java.util.Calendar import java.util.Locale import kotlin.properties.Delegates import kotlin.time.Duration +import kotlin.time.Duration.Companion.ZERO import kotlin.time.Duration.Companion.milliseconds class TimerFragment : Fragment() { @@ -25,9 +31,7 @@ class TimerFragment : Fragment() { 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(ZERO) private var started by Delegates.observable(false) { _, _, newValue -> setButtonsState(newValue) @@ -57,12 +61,18 @@ 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) } setButtonsState(started) with(binding) { - time.text = this@TimerFragment.time.toDisplayString() + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.STARTED) { + timeFlow.collect { duration -> + binding.time.text = duration.toDisplayString() + } + } + } btnStart.setOnClickListener { started = true } @@ -74,7 +84,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) } @@ -84,7 +94,7 @@ class TimerFragment : Fragment() { val prevTime = System.currentTimeMillis() while(isActive){ val curTime = System.currentTimeMillis() - time = (curTime-prevTime).milliseconds + timeFlow.emit((curTime-prevTime).milliseconds) delay(DELAY) } } From c06667b6100c21fcf7a418b7f9ff4b6fd4116b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D1=8C=D0=B1=D0=B5=D1=80=D1=82?= Date: Sun, 16 Mar 2025 20:36:55 +0500 Subject: [PATCH 3/5] Task 2.1 login coroutine --- .../ui/login/LoginViewModel.kt | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 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..d692f92 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,8 +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 kotlinx.coroutines.withContext +import ru.otus.coroutineshomework.ui.login.data.Credentials -class LoginViewModel : ViewModel() { +class LoginViewModel(private val loginApi: LoginApi = LoginApi()) : ViewModel() { private val _state = MutableLiveData(LoginViewState.Login()) val state: LiveData = _state @@ -15,13 +20,35 @@ class LoginViewModel : ViewModel() { * @param password user password */ fun login(name: String, password: String) { - // TODO: Implement login + viewModelScope.launch { + _state.value = LoginViewState.LoggingIn + try { + val user = withContext(Dispatchers.IO){ + loginApi.login(Credentials(name, password)) + } + _state.value =LoginViewState.Content(user) + } + catch (ex: Exception){ + _state.value = LoginViewState.Login(error = ex) + } + } } /** * Logout from the network */ fun logout() { - // TODO: Implement logout + viewModelScope.launch { + _state.value = LoginViewState.LoggingOut + try { + val user = withContext(Dispatchers.IO){ + loginApi.logout() + } + _state.value =LoginViewState.Login() + } + catch (ex: Exception){ + _state.value = LoginViewState.Login(error = ex) + } + } } } From a6e29078be37df9adc0e8ad0dbb85434f543f740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D1=8C=D0=B1=D0=B5=D1=80=D1=82?= Date: Sun, 16 Mar 2025 20:50:55 +0500 Subject: [PATCH 4/5] Task 2.2 login flow --- .../ui/login/LoginFragment.kt | 20 +++++++++++++------ .../ui/login/LoginViewModel.kt | 20 ++++++++++--------- 2 files changed, 25 insertions(+), 15 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..e64c8b1 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,10 @@ import android.view.ViewGroup import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.ContentBinding import ru.otus.coroutineshomework.databinding.FragmentLoginBinding import ru.otus.coroutineshomework.databinding.LoadingBinding @@ -43,12 +47,16 @@ 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 { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + loginViewModel.state.collect { state -> + when (state) { + is LoginViewState.Login -> showLogin(state) + LoginViewState.LoggingIn -> showLoggingIn() + is LoginViewState.Content -> showContent(state) + 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 d692f92..37c6a8c 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,14 +5,16 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import ru.otus.coroutineshomework.ui.login.data.Credentials class LoginViewModel(private val loginApi: LoginApi = LoginApi()) : ViewModel() { - private val _state = MutableLiveData(LoginViewState.Login()) - val state: LiveData = _state + private val _state = MutableStateFlow(LoginViewState.Login()) + val state: StateFlow = _state /** * Login to the network @@ -21,15 +23,15 @@ class LoginViewModel(private val loginApi: LoginApi = LoginApi()) : ViewModel() */ fun login(name: String, password: String) { viewModelScope.launch { - _state.value = LoginViewState.LoggingIn + _state.emit(LoginViewState.LoggingIn) try { val user = withContext(Dispatchers.IO){ loginApi.login(Credentials(name, password)) } - _state.value =LoginViewState.Content(user) + _state.emit(LoginViewState.Content(user)) } catch (ex: Exception){ - _state.value = LoginViewState.Login(error = ex) + _state.emit(LoginViewState.Login(error = ex)) } } } @@ -39,15 +41,15 @@ class LoginViewModel(private val loginApi: LoginApi = LoginApi()) : ViewModel() */ fun logout() { viewModelScope.launch { - _state.value = LoginViewState.LoggingOut + _state.emit(LoginViewState.LoggingOut) try { - val user = withContext(Dispatchers.IO){ + withContext(Dispatchers.IO){ loginApi.logout() } - _state.value =LoginViewState.Login() + _state.emit(LoginViewState.Login()) } catch (ex: Exception){ - _state.value = LoginViewState.Login(error = ex) + _state.emit(LoginViewState.Login(error = ex)) } } } From 107d21fb482bdc16275b28a378303ab3d7924726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D1=8C=D0=B1=D0=B5=D1=80=D1=82?= Date: Sun, 16 Mar 2025 21:08:11 +0500 Subject: [PATCH 5/5] Task 3 network speed --- .../ui/network/NetworkViewModel.kt | 27 ++++++++++++++++++- 1 file changed, 26 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..67846d3 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 @@ -8,6 +8,10 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import kotlin.random.Random +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.launch class NetworkViewModel : ViewModel() { @@ -18,7 +22,28 @@ class NetworkViewModel : ViewModel() { val result: LiveData = _result fun startTest(numberOfThreads: Int) { - // TODO: Implement the logic + viewModelScope.launch { + _running.value = true + + val ranJobs = mutableListOf>>() + + repeat(numberOfThreads) { + val job = async { + emulateBlockingNetworkRequest() + } + ranJobs.add(job) + } + + val results = ranJobs.mapNotNull { it.await().getOrNull() } + + if (results.isNotEmpty()) { + val averageTime = results.average().toLong() + _result.value = averageTime + } else { + _result.value = null + } + _running.value = false + } } private companion object {