Making Smart Lock for Passwords Reactive
SmartLock for passwords is a feature from Google to help users to manage passwords in mobile applications and websites. Once a password is entered, there is no need to enter it again. The credentials for the website or mobile application will be associated with the user’s Google account. Then if a user is using the same Google account, it is possible to retrieve the saved credentials for a website or an app. This is a convenient way to log in without providing username and password.
How to implement SmartLock support
Google provides good sources to learn how to enable SmartLock support in apps (info 1; codelabs), so there is no need to go through all of those steps in this article. Instead I will focus on how we can create a reactive API for Smart Lock.
How can it be improved
The main problem with Smart Lock is that it is using the method Activity.startIntentSenderForResult(). This method is used to show a dialog to save credentials. From this dialog you can also choose an existing account. This operation is asynchronous and requires input from the user. The result from the user action is delivered back to the activity method Activity.onActivityResult(). The result is then processed and an appropriate action taken.
This is a common way to work with Google Play Services. This approach violates the guidelines of clean architectures like MVP (Model-View-Presenter) or MVVM (Model-View-ViewModel). The interactions with Google Play Services have to be done in the view layer of an app - Activity or Fragment. We want to have our views as stateless as possible. Therefore they should not interact with Smart Lock.
To fix this we came to the idea to implement Smart Lock support with a reactive interface. Smart Lock methods should return RxJava types like Single or Completable. This is beneficial for us because we could handle asynchronous operations inside. Every time there is a result from Smart Lock we don’t need to handle it in Activity.onActivityResult()
, it will be delivered to the reactive stream.
Let’s look at the following interface
The interface looks straightforward enough. It provides methods to save, retrieve and delete credentials. All methods are returning RxJava types: either Completable
or Single
.
Let’s go through the implementation of one method: storeCredentials()
. The other methods are using a similar approach.
The implementation has an important detail. We want to encapsulate all Smart Lock interactions inside one single module. Therefore, we don’t want to handle the result of Activity.onActivityResult()
in every activity in which we call the method. Instead we want to handle the result in one place. To achieve this, we introduce an activity with a transparent background. From the user side, it doesn’t make any difference, but it allows us to show all Smart Lock dialogs and deliver the result only to that transparent activity.
Example
Below you can see a simplified version of the implementation of storing credentials.
As a first step we need to launch a transparent activity with a specific request code. Once an activity is created, we can continue with saving credentials. A specific request code will help to resume the operation for saving credentials. Then we need to initialize GoogleApiClient, if it was not done yet.
In the code above we initialize googleApiClient
. It is is an instance of the GoogleApiClient from Google Mobile Services. It needs to be initialized beforehand. Since initialization of the GoogleApiClient happens asynchronously we will need googleApiClientSubject
. It gets updated by the instance of the googleApiClient
when a connection is established. Otherwise it will emit an exception in case of an error.
Once, the instance of googleApiClient
exists and is ready, we can proceed with storing credentials.
Then we have to initiate saving credentials by calling Auth.CredentialsApi.save(googleApiClient, credentialsToSave)
. If the status in the callback is successful, we are done and credentials were saved successfully.
However, if the status is not successful, we have to call status.startResolutionForResult
. This is exactly the place where we need a reference to an activity. The fact that it’s transparent is a side effect of the implementation. We have used a transparent activity to initialize googleApiClient
and now we can fetch the reference to it back. The call status.startResolutionForResult()
will show a popup to save credentials and the result of it will be delivered to onActivityResult()
as a callback of the transparent activity.
A specific subject activityResultSubject
is used here to deliver the data from onActivityResult()
callback, where they can be processed.
The other methods in the SmartLockCredentialsManager
interface work in a similar way. They launch a transparent activity, it creates an instance of GoogleApiClient
if it doesn’t exist, and then the flow continues with Auth.CredentialsApi
. If any further action is required (for example, startResolutionForResult()
), then the reference to the transparent activity is used.
Also, remember to finish the transparent activity when all operations are done.
Usage
Once everything is ready, we need only to perform the following action:
rxSmartLockManager.storeCredentials(credentials)
.subscribe()
There is no need to handle the results in onActivityResult()
or listening for other callbacks. Everything is encapsulated inside.