From 91109b56c1cd787f7295f72b8933497e862e3868 Mon Sep 17 00:00:00 2001 From: atuzhilov Date: Sat, 25 Jan 2025 15:01:35 +0300 Subject: [PATCH 1/5] 1. Timer coroutines --- .../coroutineshomework/ui/timer/TimerFragment.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 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..4bb175e 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,6 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope 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 +33,8 @@ class TimerFragment : Fragment() { } } + private var timerJob: Job? = null + private fun setButtonsState(started: Boolean) { with(binding) { btnStart.isEnabled = !started @@ -44,7 +45,7 @@ class TimerFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View { _binding = FragmentTimerBinding.inflate(inflater, container, false) return binding.root @@ -75,11 +76,16 @@ class TimerFragment : Fragment() { } private fun startTimer() { - // TODO: Start timer + timerJob = lifecycleScope.launch { + while (started) { + delay(2L) + time += 2.milliseconds + } + } } private fun stopTimer() { - // TODO: Stop timer + timerJob?.cancel() } override fun onDestroyView() { From 9e182c871e4885deec7ca86f23195b6ef5f693d9 Mon Sep 17 00:00:00 2001 From: atuzhilov Date: Sat, 25 Jan 2025 23:57:14 +0300 Subject: [PATCH 2/5] 1. Timer flow --- .../ui/timer/TimerFragment.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 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 4bb175e..42cc37f 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,9 +5,12 @@ 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.launch import ru.otus.coroutineshomework.databinding.FragmentTimerBinding import java.util.Locale @@ -20,9 +23,7 @@ class TimerFragment : Fragment() { 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(Duration.ZERO) private var started by Delegates.observable(false) { _, _, newValue -> setButtonsState(newValue) @@ -54,12 +55,16 @@ class TimerFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) savedInstanceState?.let { - time = it.getLong(TIME).milliseconds + timeFlow.value = it.getLong(TIME).milliseconds started = it.getBoolean(STARTED) } setButtonsState(started) with(binding) { - time.text = this@TimerFragment.time.toDisplayString() + lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + timeFlow.collect { value -> time.text = value.toDisplayString() } + } + } btnStart.setOnClickListener { started = true } @@ -71,7 +76,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) } @@ -79,7 +84,7 @@ class TimerFragment : Fragment() { timerJob = lifecycleScope.launch { while (started) { delay(2L) - time += 2.milliseconds + timeFlow.emit(timeFlow.value + 2.milliseconds) } } } @@ -105,4 +110,4 @@ class TimerFragment : Fragment() { this.inWholeMilliseconds.toInt() ) } -} \ No newline at end of file +} From 857009a1e895beda870fc2ba7e578fb158ff2c62 Mon Sep 17 00:00:00 2001 From: atuzhilov Date: Sun, 26 Jan 2025 00:55:01 +0300 Subject: [PATCH 3/5] 2. Login coroutines --- .../ui/login/LoginViewModel.kt | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 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..eedace9 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,25 +3,43 @@ 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 +import ru.otus.coroutineshomework.ui.login.data.User class LoginViewModel : ViewModel() { private val _state = MutableLiveData(LoginViewState.Login()) val state: LiveData = _state + private val loginApi = LoginApi() + private lateinit var user: User - /** - * Login to the network - * @param name user name - * @param password user password - */ fun login(name: String, password: String) { - // TODO: Implement login + _state.value = LoginViewState.LoggingIn + viewModelScope.launch { + try { + withContext(Dispatchers.IO) { + user = loginApi.login(Credentials(name, password)) + } + _state.value = LoginViewState.Content(user) + } catch (e: IllegalArgumentException) { + withContext(Dispatchers.Main) { + _state.value = LoginViewState.Login(e) + } + } + } } - /** - * Logout from the network - */ fun logout() { - // TODO: Implement logout + _state.value = LoginViewState.LoggingOut + viewModelScope.launch { + withContext(Dispatchers.IO) { + loginApi.logout() + } + _state.value = LoginViewState.Login() + } } } From 20434aae6fa7d7441e88e526e663005753a80ba0 Mon Sep 17 00:00:00 2001 From: atuzhilov Date: Sun, 26 Jan 2025 17:23:42 +0300 Subject: [PATCH 4/5] 2. Login flow --- .../ui/login/LoginFragment.kt | 25 ++++++++---- .../ui/login/LoginViewModel.kt | 39 +++++++++++-------- 2 files changed, 40 insertions(+), 24 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..f337bdd 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 @@ -26,7 +30,7 @@ class LoginFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View { _binding = FragmentLoginBinding.inflate(inflater, container, false) @@ -43,13 +47,18 @@ 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 { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + loginViewModel.state.collect { + when (it) { + is LoginViewState.Login -> showLogin(it) + LoginViewState.LoggingIn -> showLoggingIn() + is LoginViewState.Content -> showContent(it) + LoginViewState.LoggingOut -> showLoggingOut() + } + } } + } } @@ -111,4 +120,4 @@ class LoginFragment : Fragment() { super.onDestroyView() _binding = null } -} \ No newline at end of file +} 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 eedace9..2a4a2a0 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,10 +1,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.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import ru.otus.coroutineshomework.ui.login.data.Credentials @@ -12,34 +15,38 @@ import ru.otus.coroutineshomework.ui.login.data.User class LoginViewModel : ViewModel() { - private val _state = MutableLiveData(LoginViewState.Login()) - val state: LiveData = _state + private val stateFlow = MutableStateFlow(LoginViewState.Login()) + val state: StateFlow = stateFlow private val loginApi = LoginApi() private lateinit var user: User fun login(name: String, password: String) { - _state.value = LoginViewState.LoggingIn + stateFlow.value = LoginViewState.LoggingIn viewModelScope.launch { - try { - withContext(Dispatchers.IO) { - user = loginApi.login(Credentials(name, password)) - } - _state.value = LoginViewState.Content(user) - } catch (e: IllegalArgumentException) { - withContext(Dispatchers.Main) { - _state.value = LoginViewState.Login(e) - } + loginFlow(name, password).collect { + stateFlow.value = it } } } fun logout() { - _state.value = LoginViewState.LoggingOut + stateFlow.value = LoginViewState.LoggingOut viewModelScope.launch { withContext(Dispatchers.IO) { loginApi.logout() } - _state.value = LoginViewState.Login() + stateFlow.value = LoginViewState.Login() } } + + private fun loginFlow(name: String, password: String): Flow = flow { + emit(LoginViewState.LoggingIn) + try { + user = loginApi.login(Credentials(name, password)) + emit(LoginViewState.Content(user)) + } catch (e: IllegalArgumentException) { + emit(LoginViewState.Login(e)) + + } + }.flowOn(Dispatchers.IO) } From 51ec2db984728e9947ab0dda1c394e76f9b9ae94 Mon Sep 17 00:00:00 2001 From: atuzhilov Date: Sun, 26 Jan 2025 18:46:24 +0300 Subject: [PATCH 5/5] 3. Network coroutines --- .../ui/network/NetworkViewModel.kt | 19 ++++++++++++++++++- 1 file changed, 18 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..5c084e7 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,11 @@ 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.isActive +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.random.Random @@ -18,7 +21,21 @@ class NetworkViewModel : ViewModel() { val result: LiveData = _result fun startTest(numberOfThreads: Int) { - // TODO: Implement the logic + + val resultsList = mutableListOf>() + + viewModelScope.launch { + _running.value = true + repeat(numberOfThreads) { + resultsList.add(async { emulateBlockingNetworkRequest() }.await()) + } + + val successResultsList = resultsList.mapNotNull { it.getOrNull() } + + _result.value = successResultsList.average().toLong() + _running.value = false + } + } private companion object {