The first thing that was implemented in the Open Event Android app was the login screen. This is because there were many API endpoints which cannot be accessed without an auth token. Let’s see how this was implemented.
JWT token
Firstly let’s talk about what is this token that I mentioned. The token is JWT (Javascript Web Token) which includes certain information about the user, such as identifier and information about if the token is valid, when will it expire and a signature to verify if it was tampered.
Code
Let’s have a look at the code in the Login fragment. Firstly we have a boolean to check if the user should login or not. If this variable is true we will send the user to the MainActivity. Next if the user clicks on the login button we will check the username and password and then the we will decide whether to show the progress bar or not. If the user encounters an error while logging in that error is shown to the user in a toast message.
if (loginActivityViewModel.isLoggedIn())
redirectToMain() rootView.loginButton.setOnClickListener { loginActivityViewModel.login(username.text.toString(), password.text.toString()) } loginActivityViewModel.progress.observe(this, Observer { it?.let { showProgress(it) } }) loginActivityViewModel.error.observe(this, Observer { Toast.makeText(context, it, Toast.LENGTH_LONG).show() }) loginActivityViewModel.loggedIn.observe(this, Observer { Toast.makeText(context, “Success!”, Toast.LENGTH_LONG).show() redirectToMain() }) |
All these methods and variables are declared in the ViewModel so let’s have a look there to understand them. All our variables are of the type LiveData, this is the reason why we are using the observe method in the login fragment. The observe method is used to observe these variables and whenever their value is changed it notifies the observer. In the login function we are sending a request to the server in a background thread.
val progress = MutableLiveData<Boolean>()
val error = SingleLiveEvent<String>() val loggedIn = SingleLiveEvent<Boolean>() fun isLoggedIn() = authService.isLoggedIn() fun login(email: String, password: String) { if (email.isNullOrEmpty() || password.isNullOrEmpty()) { error.value = “Email or Password cannot be empty” return } compositeDisposable.add(authService.login(email, password) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { progress.value = true }.doFinally { progress.value = false }.subscribe({ loggedIn.value = true }, { error.value = “Unable to Login. Please check your credentials” })) } |
Finally lets have a look at the login function of the AuthService class that was used above. We will throw an exception if the username or password is empty. Otherwise we will send a POST request to the server with the username and password. Then we will store the JWT access token which will be used when we will access the endpoints of the server.
fun login(username: String, password: String): Single<LoginResponse> {
if (username.isEmpty() || password.isEmpty()) throw IllegalArgumentException(“Username or password cannot be empty”) return authApi.login(Login(username, password)) .map { authHolder.token = it.accessToken it } } |
This is how the POST request for login looks
@POST(“../auth/session”)
fun login(@Body login: Login): Single<LoginResponse> |
This all that is required to create the login fragment.
Resources
- ReactiveX official documentation : http://reactivex.io/
- Vogella RxJava 2 – Tutorial : http://www.vogella.com/tutorials/RxJava/article.html
- JWT token Wikipedia: https://en.wikipedia.org/wiki/JSON_Web_Token