diff --git a/android/src/main/java/com/wbrawner/budget/ui/SplashActivity.kt b/android/src/main/java/com/wbrawner/budget/ui/SplashActivity.kt index 11b2639..7f45081 100644 --- a/android/src/main/java/com/wbrawner/budget/ui/SplashActivity.kt +++ b/android/src/main/java/com/wbrawner/budget/ui/SplashActivity.kt @@ -4,8 +4,10 @@ import android.os.Bundle import android.view.View import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer import androidx.navigation.findNavController import com.wbrawner.budget.AllowanceApplication +import com.wbrawner.budget.AsyncState import com.wbrawner.budget.R import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -28,20 +30,23 @@ class SplashActivity : AppCompatActivity(), CoroutineScope { ) } val navController = findNavController(R.id.auth_content) - launch { - val navId = try { - val user = viewModel.checkForExistingCredentials() - if (user != null) { - (application as AllowanceApplication).currentUser = user - R.id.mainActivity - } else { - R.id.loginFragment + viewModel.state.observe(this, Observer { state -> + when (state) { + is AsyncState.Success -> { + when (state.data) { + is AuthenticationState.Authenticated -> { + navController.navigate(R.id.mainActivity) + finish() + } + is AuthenticationState.Unauthenticated -> { + navController.navigate(R.id.loginFragment) + } + } } - } catch (e: Exception) { - R.id.loginFragment } - navController.navigate(navId) - if (navId == R.id.mainActivity) finish() + }) + launch { + viewModel.checkForExistingCredentials() } } } diff --git a/android/src/main/java/com/wbrawner/budget/ui/SplashViewModel.kt b/android/src/main/java/com/wbrawner/budget/ui/SplashViewModel.kt index 48f758a..33412a8 100644 --- a/android/src/main/java/com/wbrawner/budget/ui/SplashViewModel.kt +++ b/android/src/main/java/com/wbrawner/budget/ui/SplashViewModel.kt @@ -2,38 +2,35 @@ package com.wbrawner.budget.ui import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import com.wbrawner.budget.AsyncState +import com.wbrawner.budget.AsyncViewModel import com.wbrawner.budget.common.budget.BudgetRepository -import com.wbrawner.budget.common.user.User import com.wbrawner.budget.common.user.UserRepository +import com.wbrawner.budget.launch import javax.inject.Inject -class SplashViewModel : ViewModel() { +class SplashViewModel : ViewModel(), AsyncViewModel { + override val state: MutableLiveData> = MutableLiveData(AsyncState.Loading) + @Inject lateinit var budgetRepository: BudgetRepository @Inject lateinit var userRepository: UserRepository - val isLoading: MutableLiveData = MutableLiveData(false) - suspend fun checkForExistingCredentials(): User? { - return try { - val user = userRepository.getProfile() - loadBudgetData() - user + suspend fun checkForExistingCredentials() { + state.postValue(AsyncState.Success(AuthenticationState.Splash)) + val authState = try { + AuthenticationState.Authenticated } catch (ignored: Exception) { - null + AuthenticationState.Unauthenticated } + state.postValue(AsyncState.Success(authState)) } - suspend fun login(username: String, password: String): User { - isLoading.value = true - return try { - val user = userRepository.login(username, password) + fun login(username: String, password: String) = launch { + AuthenticationState.Authenticated.also { loadBudgetData() - user - } catch (e: java.lang.Exception) { - isLoading.value = false - throw e } } @@ -42,3 +39,8 @@ class SplashViewModel : ViewModel() { } } +sealed class AuthenticationState { + object Splash : AuthenticationState() + object Unauthenticated : AuthenticationState() + object Authenticated : AuthenticationState() +} \ No newline at end of file diff --git a/android/src/main/java/com/wbrawner/budget/ui/auth/LoginFragment.kt b/android/src/main/java/com/wbrawner/budget/ui/auth/LoginFragment.kt index 0b1e444..47365c6 100644 --- a/android/src/main/java/com/wbrawner/budget/ui/auth/LoginFragment.kt +++ b/android/src/main/java/com/wbrawner/budget/ui/auth/LoginFragment.kt @@ -8,23 +8,17 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer -import androidx.navigation.fragment.findNavController -import com.wbrawner.budget.AllowanceApplication +import com.wbrawner.budget.AsyncState import com.wbrawner.budget.R import com.wbrawner.budget.ui.SplashViewModel import com.wbrawner.budget.ui.ensureNotEmpty import com.wbrawner.budget.ui.show import kotlinx.android.synthetic.main.fragment_login.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext /** * A simple [Fragment] subclass. */ -class LoginFragment : Fragment(), CoroutineScope { - override val coroutineContext: CoroutineContext = Dispatchers.Main +class LoginFragment : Fragment() { private val viewModel: SplashViewModel by activityViewModels() override fun onCreateView( @@ -35,14 +29,18 @@ class LoginFragment : Fragment(), CoroutineScope { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.isLoading.observe(viewLifecycleOwner, Observer { isLoading -> - formPrompt.show(!isLoading) - usernameContainer.show(!isLoading) - passwordContainer.show(!isLoading) - submit.show(!isLoading) - registerButton.show(!isLoading) - forgotPasswordLink.show(!isLoading) - progressBar.show(isLoading) + viewModel.state.observe(viewLifecycleOwner, Observer { state -> + when (state) { + is AsyncState.Loading -> { + handleLoading(true) + } + is AsyncState.Error -> { + handleLoading(false) + username.error = "Invalid username/password" + password.error = "Invalid username/password" + state.exception.printStackTrace() + } + } }) password.setOnEditorActionListener { _, _, _ -> submit.performClick() @@ -51,18 +49,7 @@ class LoginFragment : Fragment(), CoroutineScope { if (!username.ensureNotEmpty() || !password.ensureNotEmpty()) { return@setOnClickListener } - launch { - try { - val user = viewModel.login(username.text.toString(), password.text.toString()) - (requireActivity().application as AllowanceApplication).currentUser = user - findNavController().navigate(R.id.mainActivity) - activity?.finish() - } catch (e: Exception) { - username.error = "Invalid username/password" - password.error = "Invalid username/password" - e.printStackTrace() - } - } + viewModel.login(username.text.toString(), password.text.toString()) } val usernameString = arguments?.getString(EXTRA_USERNAME) val passwordString = arguments?.getString(EXTRA_PASSWORD) @@ -73,6 +60,16 @@ class LoginFragment : Fragment(), CoroutineScope { } } + private fun handleLoading(isLoading: Boolean) { + formPrompt.show(!isLoading) + usernameContainer.show(!isLoading) + passwordContainer.show(!isLoading) + submit.show(!isLoading) + registerButton.show(!isLoading) + forgotPasswordLink.show(!isLoading) + progressBar.show(isLoading) + } + companion object { const val EXTRA_USERNAME = "username" const val EXTRA_PASSWORD = "password" diff --git a/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkUserRepository.kt b/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkUserRepository.kt index c06f908..dd12f35 100644 --- a/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkUserRepository.kt +++ b/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkUserRepository.kt @@ -1,17 +1,36 @@ package com.wbrawner.budget.lib.repository +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import com.wbrawner.budget.common.user.LoginRequest import com.wbrawner.budget.common.user.User import com.wbrawner.budget.common.user.UserRepository import com.wbrawner.budget.lib.network.BudgetApiService +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import javax.inject.Inject class NetworkUserRepository @Inject constructor(private val apiService: BudgetApiService) : UserRepository { - override suspend fun login(username: String, password: String): User = - apiService.login(LoginRequest(username, password)) + override val currentUser: LiveData = MutableLiveData() - override suspend fun getProfile(): User = apiService.getProfile() + init { + GlobalScope.launch { + try { + getProfile() + } catch (ignored: Exception) { + } + } + } + + override suspend fun login(username: String, password: String): User = + apiService.login(LoginRequest(username, password)).also { + (currentUser as MutableLiveData).postValue(it) + } + + override suspend fun getProfile(): User = apiService.getProfile().also { + (currentUser as MutableLiveData).postValue(it) + } override suspend fun create(newItem: User): User = apiService.newUser(newItem) diff --git a/common/src/main/java/com/wbrawner/budget/common/user/UserRepository.kt b/common/src/main/java/com/wbrawner/budget/common/user/UserRepository.kt index 9a670cc..d257fc2 100644 --- a/common/src/main/java/com/wbrawner/budget/common/user/UserRepository.kt +++ b/common/src/main/java/com/wbrawner/budget/common/user/UserRepository.kt @@ -1,8 +1,10 @@ package com.wbrawner.budget.common.user +import androidx.lifecycle.LiveData import com.wbrawner.budget.common.Repository interface UserRepository : Repository { + val currentUser: LiveData suspend fun login(username: String, password: String): User suspend fun getProfile(): User suspend fun findAll(accountId: Long? = null): List