Android quick recipes: observe when your user has logged in from the deep down of the AccountManager

Juan Mengual
androidxx
4 min readJul 13, 2022

--

A pretty common requirement in almost any app is to have some screen which need to change if the user found that your features (well… the apps) are worth login in. But, funny thing, your login might be triggered from one screen and you might want to react to it from another different place. Wouldn’t be nice to just place an observer to something and get it called when the user logs in? And wouldn’t be even greater to not to even think in Event Bus or any other singleton kind of way to do it?

Ok, that’s the problem we are gonna solve. But first let put a precondition, one you should be probably be doing already.

A little bit of (syntactic) sugar hasn’t harmed anyone (Photo by Thomas Kelley on Unsplash)

AccountManager

When you send the user name and password to the backend, most likely you are getting some kind of token back. There is one official place in android to store your tokens and thats the AccountManager. This post assumes that you know AccountManager and that you already store your tokens there. If you don’t know anything about it, for this post you only need to be aware that it provides methods to store accounts and its tokens. It also provides methods to retrieve that data. It does not track which user is logged in your app for the case where you only support multiple accounts, so our sample will allow just one account.

Show me the code

First thing first, here is the repository with a sample app you can play with
https://github.com/juanmeanwhile/AccountManagerFlow

Observing the Account manager as a flow

AccountManager allows to add a listener to be updated about changes which we are gonna use it but transforming it into a Flow. We will create a new extension to AccountManager that convert the callback to a Flow with the handy callbackFlow.

fun AccountManager.accountStateFlow(): Flow<LoginStatus> = callbackFlow {
val listener = provideAccountManagerListener { status ->
trySendBlocking(status)
.onFailure { throwable ->
// Downstream has been cancelled or failed, can log here
}
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
addOnAccountsUpdatedListener(listener, null, true, arrayOf(ACCOUNT_TYPE))
} else {
addOnAccountsUpdatedListener(listener, null, true)
}

awaitClose {
removeOnAccountsUpdatedListener(listener)
}
}

The method provideAccountManagerListener is just generating different listener depending on the current android api level.

private fun provideAccountManagerListener(onStatus: (status: LoginStatus) -> ChannelResult<Unit>): OnAccountsUpdateListener {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AboveOreoAccountUpdateListener(onStatus)
} else {
BelowOreoAccountUpdateListener(onStatus)
}
}

Both listeners are basically the same, so let’s take a look to only one of them for simplicity, but you can check the other in the repository.

open class AboveOreoAccountUpdateListener(private val action: (status: LoginStatus) -> ChannelResult<Unit>) : OnAccountsUpdateListener {
override fun onAccountsUpdated(accounts: Array<out Account>?) {
val status = if (accounts?.isNotEmpty() == true) {
LoginStatus.LoggedIn(accounts[0].name)
} else {
LoginStatus.LoggedOut
}
action(status)
}
}

Whenever the accounts are updated, we are gonna check if there is any account registered and return LoginStatus.LoggedIn if there is any. We assumed that we allow only one account, but if you allow more than one account stored at the same time, then you can have extra logic here. If there is no account, then emit LoginStatus.LoggedOut.

Look! A new user just logged in and is about to flow out from the AccountManager (Photo by Micaela Parente on Unsplash)

Observe the flow at the ViewModel and do whatever you want with it

Once that we have this extension we can just use it in our ViewModel and expose it to the ui.

class MainViewModel @Inject constructor(accountManager: AccountManager): ViewModel() {

/**
* Exposes LoginState to the UI
*/
val uiState = accountManager.accountStateFlow().asLiveData()
}

Whenever there are changes in the flow, the ViewModel will receive them and pass them to the UI.

That’s it, we have a Flow that emits changes when the user goes from logged out to logged in and the other way around. Also, this updates are independent on which screen is triggering login and relies in Android as single source of true for user accounts.

Bonus tip: use the power of Flow

For this sample project we re keeping things pretty simple and we are just passing the login state to the UI, but now that we have a Flow we can use its operators for very nice results. In the following example, we’ll use an imaginary UserRepository to fetch user data when the user is logged in and pass that to the UI:

val uiState: Flow<UserData?> = accountManager.accountStateFlow()
.mapLatest { loginStatus ->
if (loginStatus == LoginStatus.LoggedIn) {
userRepo.getUserData()
} else {
null
}
}

With this, we’ve reached the end of this quick recipe, I hope that you find it useful and thanks for reading to the end.

--

--