Fix user loading when returning from background

This commit is contained in:
William Brawner 2020-08-31 13:51:28 -07:00
parent 1b0afe3753
commit c8f5a92e12
5 changed files with 85 additions and 60 deletions

View file

@ -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()
}
}
}

View file

@ -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<AuthenticationState> {
override val state: MutableLiveData<AsyncState<AuthenticationState>> = MutableLiveData(AsyncState.Loading)
@Inject
lateinit var budgetRepository: BudgetRepository
@Inject
lateinit var userRepository: UserRepository
val isLoading: MutableLiveData<Boolean> = 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()
}

View file

@ -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"

View file

@ -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<User?> = 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)

View file

@ -1,8 +1,10 @@
package com.wbrawner.budget.common.user
import androidx.lifecycle.LiveData
import com.wbrawner.budget.common.Repository
interface UserRepository : Repository<User, Long> {
val currentUser: LiveData<User?>
suspend fun login(username: String, password: String): User
suspend fun getProfile(): User
suspend fun findAll(accountId: Long? = null): List<User>