Skip to content

Commit 50a4ec7

Browse files
authored
Add support for Firebase backed silent sign-in (#1212)
2 parents 96b3467 + a234df1 commit 50a4ec7

File tree

5 files changed

+153
-6
lines changed

5 files changed

+153
-6
lines changed

app/src/main/java/com/firebase/uidemo/auth/AuthUiActivity.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import android.content.Intent;
1919
import android.os.Bundle;
2020
import android.support.annotation.DrawableRes;
21+
import android.support.annotation.NonNull;
2122
import android.support.annotation.Nullable;
2223
import android.support.annotation.StringRes;
2324
import android.support.annotation.StyleRes;
@@ -26,7 +27,6 @@
2627
import android.support.v7.app.AppCompatDelegate;
2728
import android.util.Log;
2829
import android.view.View;
29-
import android.widget.Button;
3030
import android.widget.CheckBox;
3131
import android.widget.CompoundButton;
3232
import android.widget.CompoundButton.OnCheckedChangeListener;
@@ -39,6 +39,9 @@
3939
import com.firebase.ui.auth.IdpResponse;
4040
import com.firebase.uidemo.R;
4141
import com.google.android.gms.common.Scopes;
42+
import com.google.android.gms.tasks.OnCompleteListener;
43+
import com.google.android.gms.tasks.Task;
44+
import com.google.firebase.auth.AuthResult;
4245
import com.google.firebase.auth.FirebaseAuth;
4346

4447
import java.util.ArrayList;
@@ -60,7 +63,6 @@ public class AuthUiActivity extends AppCompatActivity {
6063
private static final int RC_SIGN_IN = 100;
6164

6265
@BindView(R.id.root) View mRootView;
63-
@BindView(R.id.sign_in) Button mSignIn;
6466

6567
@BindView(R.id.google_provider) CheckBox mUseGoogleProvider;
6668
@BindView(R.id.facebook_provider) CheckBox mUseFacebookProvider;
@@ -166,6 +168,21 @@ public void signIn(View view) {
166168
RC_SIGN_IN);
167169
}
168170

171+
@OnClick(R.id.sign_in_silent)
172+
public void silentSignIn(View view) {
173+
AuthUI.getInstance().silentSignIn(this, getSelectedProviders())
174+
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
175+
@Override
176+
public void onComplete(@NonNull Task<AuthResult> task) {
177+
if (task.isSuccessful()) {
178+
startSignedInActivity(null);
179+
} else {
180+
showSnackbar(R.string.sign_in_failed);
181+
}
182+
}
183+
});
184+
}
185+
169186
@Override
170187
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
171188
super.onActivityResult(requestCode, resultCode, data);

app/src/main/res/layout/auth_ui_layout.xml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,18 @@
3535
android:layout_width="wrap_content"
3636
android:layout_height="wrap_content"
3737
android:layout_gravity="center"
38-
android:layout_margin="16dp"
38+
android:layout_marginTop="16dp"
3939
android:text="@string/sign_in_start" />
4040

41+
<Button
42+
android:id="@+id/sign_in_silent"
43+
style="@style/Widget.AppCompat.Button.Colored"
44+
android:layout_width="wrap_content"
45+
android:layout_height="wrap_content"
46+
android:layout_gravity="center"
47+
android:layout_marginBottom="16dp"
48+
android:text="@string/sign_in_silent" />
49+
4150
<TextView
4251
style="@style/Base.TextAppearance.AppCompat.Subhead"
4352
android:layout_width="wrap_content"

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<!-- Auth UI -->
1818
<string name="launch_title">FirebaseUI Auth Demo</string>
1919
<string name="sign_in_start">Start</string>
20+
<string name="sign_in_silent">Sign in silently</string>
2021

2122
<string name="providers_header">Auth providers</string>
2223
<string name="providers_google">Google</string>
@@ -72,6 +73,7 @@
7273
<string name="sign_out">Sign out</string>
7374
<string name="delete_account_label">Delete account</string>
7475

76+
<string name="sign_in_failed">Sign in failed</string>
7577
<string name="sign_out_failed">Sign out failed</string>
7678
<string name="delete_account_failed">Delete account failed</string>
7779

auth/README.md

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ and [Web](https://github.com/firebase/firebaseui-web/).
3333
1. [Configuration](#configuration)
3434
1. [Provider config](#identity-provider-configuration)
3535
1. [Usage instructions](#using-firebaseui-for-authentication)
36-
1. [Sign in](#sign-in)
36+
1. [AuthUI sign-in](#authui-sign-in)
3737
1. [Handling responses](#handling-the-sign-in-response)
38+
1. [Silent sign-in](#silent-sign-in)
3839
1. [Sign out](#sign-out)
3940
1. [Account deletion](#deleting-accounts)
4041
1. [Auth flow chart](#authentication-flow-chart)
@@ -169,7 +170,7 @@ If an alternative app instance is required, call
169170
`AuthUI.getInstance(app)` instead, passing the appropriate `FirebaseApp`
170171
instance.
171172

172-
### Sign in
173+
### AuthUI sign-in
173174

174175
If a user is not currently signed in, as can be determined by checking
175176
`auth.getCurrentUser() != null` (where `auth` is the `FirebaseAuth` instance
@@ -352,7 +353,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
352353
showSnackbar(R.string.no_internet_connection);
353354
return;
354355
}
355-
356+
356357
showSnackbar(R.string.unknown_error);
357358
Log.e(TAG, "Sign-in error: ", response.getError());
358359
}
@@ -401,6 +402,40 @@ if (metadata.getCreationTimestamp() == metadata.getLastSignInTimestamp()) {
401402
}
402403
```
403404

405+
### Silent sign-in
406+
407+
If a user is not currently signed in, then a silent sign-in process can be started first before
408+
displaying any UI to provide a seamless experience. Silent sign-in uses saved Smart Lock credentials
409+
and returns a successful `Task` only if the user has been fully signed in with Firebase.
410+
411+
Here's an example of how you could use silent sign-in paired with Firebase anonymous sign-in to get
412+
your users up and running as fast as possible:
413+
414+
```java
415+
List<IdpConfig> providers = getSelectedProviders();
416+
AuthUI.getInstance().silentSignIn(this, providers)
417+
.continueWithTask(this, new Continuation<AuthResult, Task<AuthResult>>() {
418+
@Override
419+
public Task<AuthResult> then(@NonNull Task<AuthResult> task) {
420+
if (task.isSuccessful()) {
421+
return task;
422+
} else {
423+
// Ignore any exceptions since we don't care about credential fetch errors.
424+
return FirebaseAuth.getInstance().signInAnonymously();
425+
}
426+
}
427+
}).addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
428+
@Override
429+
public void onComplete(@NonNull Task<AuthResult> task) {
430+
if (task.isSuccessful()) {
431+
// Signed in! Start loading data
432+
} else {
433+
// Uh oh, show error message
434+
}
435+
}
436+
});
437+
```
438+
404439
### Sign out
405440

406441
With the integrations provided by AuthUI, signing out a user is a multi-stage process:

auth/src/main/java/com/firebase/ui/auth/AuthUI.java

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@
4141
import com.firebase.ui.auth.util.data.PhoneNumberUtils;
4242
import com.firebase.ui.auth.util.data.ProviderUtils;
4343
import com.google.android.gms.auth.api.credentials.Credential;
44+
import com.google.android.gms.auth.api.credentials.CredentialRequest;
45+
import com.google.android.gms.auth.api.credentials.CredentialRequestResponse;
4446
import com.google.android.gms.auth.api.credentials.CredentialsClient;
4547
import com.google.android.gms.auth.api.signin.GoogleSignIn;
48+
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
4649
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
4750
import com.google.android.gms.common.api.ApiException;
4851
import com.google.android.gms.common.api.CommonStatusCodes;
@@ -51,6 +54,8 @@
5154
import com.google.android.gms.tasks.Task;
5255
import com.google.android.gms.tasks.Tasks;
5356
import com.google.firebase.FirebaseApp;
57+
import com.google.firebase.auth.AuthCredential;
58+
import com.google.firebase.auth.AuthResult;
5459
import com.google.firebase.auth.EmailAuthProvider;
5560
import com.google.firebase.auth.FacebookAuthProvider;
5661
import com.google.firebase.auth.FirebaseAuth;
@@ -267,6 +272,85 @@ public static int getDefaultTheme() {
267272
return R.style.FirebaseUI;
268273
}
269274

275+
/**
276+
* Signs the user in without any UI if possible. If this operation fails, you can safely start a
277+
* UI-based sign-in flow knowing it is required.
278+
*
279+
* @param context requesting the user be signed in
280+
* @param desiredConfigs to use for silent sign in. Only Google and email are currently
281+
* supported, the rest will be ignored.
282+
* @return a task which indicates whether or not the user was successfully signed in.
283+
*/
284+
@NonNull
285+
public Task<AuthResult> silentSignIn(@NonNull Context context,
286+
@NonNull List<IdpConfig> configs) {
287+
if (mAuth.getCurrentUser() != null) {
288+
throw new IllegalArgumentException("User already signed in!");
289+
}
290+
291+
final Context appContext = context.getApplicationContext();
292+
final IdpConfig google =
293+
ProviderUtils.getConfigFromIdps(configs, GoogleAuthProvider.PROVIDER_ID);
294+
final IdpConfig email =
295+
ProviderUtils.getConfigFromIdps(configs, EmailAuthProvider.PROVIDER_ID);
296+
297+
if (google == null && email == null) {
298+
throw new IllegalArgumentException("No supported providers were supplied. " +
299+
"Add either Google or email support.");
300+
}
301+
302+
final GoogleSignInOptions googleOptions;
303+
if (google == null) {
304+
googleOptions = null;
305+
} else {
306+
GoogleSignInAccount last = GoogleSignIn.getLastSignedInAccount(appContext);
307+
if (last != null && last.getIdToken() != null) {
308+
return mAuth.signInWithCredential(GoogleAuthProvider.getCredential(
309+
last.getIdToken(), null));
310+
}
311+
312+
googleOptions = google.getParams()
313+
.getParcelable(ExtraConstants.GOOGLE_SIGN_IN_OPTIONS);
314+
}
315+
316+
return GoogleApiUtils.getCredentialsClient(context)
317+
.request(new CredentialRequest.Builder()
318+
// We can support both email and Google at the same time here because they
319+
// are mutually exclusive. If a user signs in with Google, their email
320+
// account will automatically be upgraded (a.k.a. replaced) with the Google
321+
// one, meaning Smart Lock won't have to show the picker UI.
322+
.setPasswordLoginSupported(email != null)
323+
.setAccountTypes(google == null ? null :
324+
ProviderUtils.providerIdToAccountType(GoogleAuthProvider.PROVIDER_ID))
325+
.build())
326+
.continueWithTask(new Continuation<CredentialRequestResponse, Task<AuthResult>>() {
327+
@Override
328+
public Task<AuthResult> then(@NonNull Task<CredentialRequestResponse> task) {
329+
Credential credential = task.getResult().getCredential();
330+
String email = credential.getId();
331+
String password = credential.getPassword();
332+
333+
if (TextUtils.isEmpty(password)) {
334+
return GoogleSignIn.getClient(appContext,
335+
new GoogleSignInOptions.Builder(googleOptions)
336+
.setAccountName(email)
337+
.build())
338+
.silentSignIn()
339+
.continueWithTask(new Continuation<GoogleSignInAccount, Task<AuthResult>>() {
340+
@Override
341+
public Task<AuthResult> then(@NonNull Task<GoogleSignInAccount> task) {
342+
AuthCredential authCredential = GoogleAuthProvider.getCredential(
343+
task.getResult().getIdToken(), null);
344+
return mAuth.signInWithCredential(authCredential);
345+
}
346+
});
347+
} else {
348+
return mAuth.signInWithEmailAndPassword(email, password);
349+
}
350+
}
351+
});
352+
}
353+
270354
/**
271355
* Signs the current user out, if one is signed in.
272356
*

0 commit comments

Comments
 (0)