Last active
March 23, 2018 22:18
-
-
Save ZakTaccardi/a854c79f140f8b18b202a3768b5874eb to your computer and use it in GitHub Desktop.
Reactive Login
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.app.Activity | |
import io.reactivex.Observable | |
import io.reactivex.disposables.CompositeDisposable | |
import io.reactivex.rxkotlin.addTo | |
import io.reactivex.rxkotlin.subscribeBy | |
/** | |
* Stores state related to login. | |
*/ | |
interface LoginRepository { | |
// A stream of login states | |
fun stateStream(): Observable<LoginState> | |
// one way to log in | |
fun submitCredentials(username: String, password: String) | |
// another way to login | |
fun submitFingerPrint(isValid: Boolean) | |
} | |
/** | |
* Represents state of the Logged in User. | |
* | |
* - bonus points: log the state to the console for easy debugging | |
*/ | |
data class LoginState( | |
val isLoggedIn: Boolean, | |
val isAuthorizing: Boolean // true when network call to validate credentials is in progress | |
) | |
class LoginActivity : Activity() { | |
private lateinit var loginRepository: LoginRepository | |
private val startDisposables = CompositeDisposable() | |
override fun onStart() { | |
super.onStart() | |
// when the user is logged in, go to the main activity | |
loginRepository.stateStream() | |
.filter { state: LoginState -> state.isLoggedIn } | |
.firstOrError() | |
.subscribeBy { goToMainActivity() } | |
.addTo(startDisposables) // unsubscribe in onStop() | |
loginRepository.stateStream() | |
.map { state: LoginState -> state.isAuthorizing } | |
.filter { isAuthorizing: Boolean -> isAuthorizing == true } | |
.distinctUntilChanged() // ignore duplicate emissions | |
.subscribeBy { isAuthorizingInProgress: Boolean -> | |
if (isAuthorizingInProgress) { | |
enableInput(false) // disable input while network call is running | |
showLoadingProgressBar(true) | |
} else { | |
enableInput(true) // enable input while network call is not running | |
showLoadingProgressBar(false) | |
} | |
} | |
.addTo(startDisposables) // avoid memory leak | |
} | |
override fun onStop() { | |
startDisposables.clear() // avoid memory leaks | |
super.onStop() | |
} | |
fun onClickLogin() { | |
val username = "" // extract username | |
val password = "" // extract password | |
// this is void. we don't need a return value because we are already observing the login | |
// state in `onStart()` to go to the MainActivity. | |
// - bonus points: no memory leaks | |
loginRepository.submitCredentials(username, password) | |
} | |
fun goToMainActivity() { | |
// fire intent.. | |
} | |
fun onFingerprint(isFingerprintValid: Boolean) { | |
// we've now added a second way to login the user. | |
// - notice we don't have to add new code to handle the result, because it's already being | |
// observed in `onStart()` | |
// - imagine this was part of a child fragment instead. no need to grab a reference to the | |
// parent activity and do ugly casting to call `goToMainActivity()` | |
loginRepository.submitFingerPrint(isFingerprintValid) | |
} | |
fun enableInput(enable: Boolean = true) { | |
if (enable) { | |
// enable user input | |
} else { | |
// disable user input | |
} | |
} | |
fun showLoadingProgressBar(show: Boolean = true) { | |
if (show) { | |
// show loading indicator in UI | |
} else { | |
// hide loading indicator | |
} | |
} | |
} | |
// A base activity for activities that requires user to be logged in | |
abstract class BaseRequiresAuthActivity : Activity() { | |
lateinit var loginRepository: LoginRepository | |
private val startDisposables = CompositeDisposable() | |
override fun onStart() { | |
super.onStart() | |
// launch login activity when user is logged out | |
loginRepository.stateStream() | |
.filter { state -> !state.isLoggedIn } | |
.firstOrError() | |
.subscribeBy { goToLoginActivity() } | |
.addTo(startDisposables) | |
} | |
fun goToLoginActivity() { | |
// finish this activity, fire login activity intent | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
LoginRepository
storesLoginState
, which activities observe. UI can request to update theLoginState
by sending input that theLoginRepository
controls