Android Firebase 使用Email認證與登入

開始之前, 需要先完成Firebase的註冊和AndroidStudio的環境設定
請參考文章
Android - Firebase 註冊
Android - Firebase 專案開發準備工作

接下來我們會以Database的讀寫, 來測試Email認證成功後, 是否可讀到資料庫資訊.

Android-開發Firebase專案-Database篇中提到, 如果使用預設的安全性時, 是無法取得Firebase的資料.

因此我們必須先認證後, 才能讀取資料.

Step 1: Connect to Firebase

如果有在Android-開發Firebase專案-Database篇中設定過, 可跳過此步驟

若專案還未連接過Firebase, 請參閱Android-開發Firebase專案-Database篇中的 Step 1 ~ Step 2

並在Step 2 將選擇Realtime Database更改為選擇「Authentication」, 再點擊「Email and password authentication」

Step 2: 將 Authentication 引入 Module

選取「Add Firebase Authentication to your app」

Step 3: 建立Email認證

到Firebase網頁的專案頁面中, 點擊左邊的「Authentication」

再選取「登入方式」TAB

將「電子郵件/密碼」選項打開

Step 4: 檢查是否已登入

到主程式中, 先取得Firebase Instance

1
private FirebaseAuth fileAuth = FirebaseAuth.getInstance();

建立一個LoginActivity, 來輸入帳號密碼

設定授權狀態監聽器 AuthStateListener, 當未登入時, 開啟登入畫面LoginActivity

1
2
3
4
5
6
7
8
9
10
11
12
private FirebaseAuth.AuthStateListener authStateListener;
authStateListener = new FirebaseAuth.AuthStateListener() {
@Override
public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user==null) {
startActivity(new Intent(MainActivity.this, LoginActivity.class));
}else{
// TODO after login
}
}
};

在onStart階段註冊監聽, onStop階段取消監聽

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void onStart() {
super.onStart();
fileAuth.addAuthStateListener(authStateListener);
}

@Override
protected void onStop() {
if (authStateListener != null){
fileAuth.removeAuthStateListener(authStateListener);
}
super.onStop();
}

Step 5: 登入Firebase

在LoginActivity.java / attemptLogin 函數中, 我們不用預設的AsyncTask : UserLoginTask來登入, 改為FirebaseAuth 的登入方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//mAuthTask = new UserLoginTask(email, password);
//mAuthTask.execute((Void) null);

FirebaseAuth auth = FirebaseAuth.getInstance();
auth.signInWithEmailAndPassword(email, password)
.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
@Override
public void onSuccess(AuthResult authResult) {
Log.d(TAG, "登入成功");
showProgress(false);
finish();
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.w(TAG, "登入失敗,請檢查email/password");
showProgress(false);
finish();
}
});

Step 6: 登入成功後, 更新Database

程式一開始時, 由於還未登入認證, 所以無法取得Firebase的資料
Log 顯示如下

登入的結果會由MainActivity 的 FirebaseAuth.AuthStateListener() 返回結果
當登入成功時, 我們設置FirebaseDatabase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
authStateListener = new FirebaseAuth.AuthStateListener() {
@Override
public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
Log.d(TAG, "onAuthStateChanged");
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user==null) {
Log.d(TAG, "Try to Login: ");
startActivity(new Intent(MainActivity.this, LoginActivity.class));
} else {
Log.d(TAG, "Login Success");
FirebaseDatabase fireDB = FirebaseDatabase.getInstance();
DatabaseReference myRef = fireDB.getReference("聯絡人");
myRef.addChildEventListener(MainActivity.this);
}
}
};

Step 7: Logout

一般而言, 當登入成功後, 倘若下一次希望能夠自動登入使用者帳密時,
需要程式開發人員自行撰寫code, 去紀錄使用者登入的帳密.

Friebase已在底層做好此功能, 只要驗證過一次, 下次再執行App時, 就能自動登入.

若要使用者下次執行App時, 需要再輸入帳密時
可在onDestory階段, 執行FirebaseAuth.signOut() 即可

1
2
3
4
5
@Override
protected void onDestroy() {
fileAuth.signOut();
super.onDestroy();
}

Source Code

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;

import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity implements ChildEventListener {

ArrayAdapter<String> fileDBAdapter;

private FirebaseAuth fileAuth;
private FirebaseAuth.AuthStateListener authStateListener;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ListView list = (ListView) findViewById(R.id.listView);
fileDBAdapter =
new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1,
android.R.id.text1);
list.setAdapter(fileDBAdapter);

fileAuth = FirebaseAuth.getInstance();
authStateListener = new FirebaseAuth.AuthStateListener() {
@Override
public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
Log.e("Nick", "onAuthStateChanged");
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user==null) {
Log.e("Nick", "Try to Login: ");
startActivity(new Intent(MainActivity.this, LoginActivity.class));
} else {
Log.e("Nick", "Login Success");
FirebaseDatabase fireDB = FirebaseDatabase.getInstance();
DatabaseReference myRef = fireDB.getReference("聯絡人");
myRef.addChildEventListener(MainActivity.this);
}
}
};
}

@Override
protected void onStart() {
super.onStart();
fileAuth.addAuthStateListener(authStateListener);
}

@Override
protected void onStop() {
if (authStateListener != null){
fileAuth.removeAuthStateListener(authStateListener);
}
super.onStop();
}

@Override
protected void onDestroy() {
fileAuth.signOut();
super.onDestroy();
}

@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
fileDBAdapter.add(
String.valueOf(dataSnapshot.child("name").getValue()));
}

@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
fileDBAdapter.remove(
String.valueOf(dataSnapshot.child("name").getValue()));
}

@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) { }

@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) { }

@Override
public void onCancelled(DatabaseError databaseError) { }
}
LoginActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.app.LoaderManager.LoaderCallbacks;

import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;

import android.os.Build;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

import static android.Manifest.permission.READ_CONTACTS;

/**
* A login screen that offers login via email/password.
*/
public class LoginActivity extends AppCompatActivity implements LoaderCallbacks<Cursor> {

/**
* Id to identity READ_CONTACTS permission request.
*/
private static final int REQUEST_READ_CONTACTS = 0;

/**
* A dummy authentication store containing known user names and passwords.
* TODO: remove after connecting to a real authentication system.
*/
private static final String[] DUMMY_CREDENTIALS = new String[]{
"foo@example.com:hello", "bar@example.com:world"
};
/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
private UserLoginTask mAuthTask = null;

// UI references.
private AutoCompleteTextView mEmailView;
private EditText mPasswordView;
private View mProgressView;
private View mLoginFormView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// Set up the login form.
mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
populateAutoComplete();

mPasswordView = (EditText) findViewById(R.id.password);
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
}
});

Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
mEmailSignInButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
attemptLogin();
}
});

mLoginFormView = findViewById(R.id.login_form);
mProgressView = findViewById(R.id.login_progress);
}

private void populateAutoComplete() {
if (!mayRequestContacts()) {
return;
}

getLoaderManager().initLoader(0, null, this);
}

private boolean mayRequestContacts() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
return true;
}
if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
.setAction(android.R.string.ok, new View.OnClickListener() {
@Override
@TargetApi(Build.VERSION_CODES.M)
public void onClick(View v) {
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
}
});
} else {
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
}
return false;
}

/**
* Callback received when a permissions request has been completed.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_READ_CONTACTS) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
populateAutoComplete();
}
}
}


/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
private void attemptLogin() {
if (mAuthTask != null) {
return;
}

// Reset errors.
mEmailView.setError(null);
mPasswordView.setError(null);

// Store values at the time of the login attempt.
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();

boolean cancel = false;
View focusView = null;

// Check for a valid password, if the user entered one.
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
mPasswordView.setError(getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}

// Check for a valid email address.
if (TextUtils.isEmpty(email)) {
mEmailView.setError(getString(R.string.error_field_required));
focusView = mEmailView;
cancel = true;
} else if (!isEmailValid(email)) {
mEmailView.setError(getString(R.string.error_invalid_email));
focusView = mEmailView;
cancel = true;
}

if (cancel) {
// There was an error; don't attempt login and focus the first
// form field with an error.
focusView.requestFocus();
} else {
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
showProgress(true);
//mAuthTask = new UserLoginTask(email, password);
//mAuthTask.execute((Void) null);

FirebaseAuth auth = FirebaseAuth.getInstance();
auth.signInWithEmailAndPassword(email, password)
.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
@Override
public void onSuccess(AuthResult authResult) {
Log.e("Nick", "登入成功");
showProgress(false);
finish();
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.e("Nick", "登入失敗,請檢查email/password");
showProgress(false);
finish();
}
});

}
}

private boolean isEmailValid(String email) {
//TODO: Replace this with your own logic
return email.contains("@");
}

private boolean isPasswordValid(String password) {
//TODO: Replace this with your own logic
return password.length() > 4;
}

/**
* Shows the progress UI and hides the login form.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private void showProgress(final boolean show) {
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
// for very easy animations. If available, use these APIs to fade-in
// the progress spinner.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);

mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});

mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mProgressView.animate().setDuration(shortAnimTime).alpha(
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
} else {
// The ViewPropertyAnimator APIs are not available, so simply show
// and hide the relevant UI components.
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}

@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this,
// Retrieve data rows for the device user's 'profile' contact.
Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,

// Select only email addresses.
ContactsContract.Contacts.Data.MIMETYPE +
" = ?", new String[]{ContactsContract.CommonDataKinds.Email
.CONTENT_ITEM_TYPE},

// Show primary email addresses first. Note that there won't be
// a primary email address if the user hasn't specified one.
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
}

@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
List<String> emails = new ArrayList<>();
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
emails.add(cursor.getString(ProfileQuery.ADDRESS));
cursor.moveToNext();
}

addEmailsToAutoComplete(emails);
}

@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {

}

private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
//Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
ArrayAdapter<String> adapter =
new ArrayAdapter<>(LoginActivity.this,
android.R.layout.simple_dropdown_item_1line, emailAddressCollection);

mEmailView.setAdapter(adapter);
}


private interface ProfileQuery {
String[] PROJECTION = {
ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
};

int ADDRESS = 0;
int IS_PRIMARY = 1;
}

/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {

private final String mEmail;
private final String mPassword;

UserLoginTask(String email, String password) {
mEmail = email;
mPassword = password;
}

@Override
protected Boolean doInBackground(Void... params) {
// TODO: attempt authentication against a network service.

try {
// Simulate network access.
Thread.sleep(2000);
} catch (InterruptedException e) {
return false;
}

for (String credential : DUMMY_CREDENTIALS) {
String[] pieces = credential.split(":");
if (pieces[0].equals(mEmail)) {
// Account exists, return true if the password matches.
return pieces[1].equals(mPassword);
}
}

// TODO: register the new account here.
return true;
}

@Override
protected void onPostExecute(final Boolean success) {
mAuthTask = null;
showProgress(false);

if (success) {
finish();
} else {
mPasswordView.setError(getString(R.string.error_incorrect_password));
mPasswordView.requestFocus();
}
}

@Override
protected void onCancelled() {
mAuthTask = null;
showProgress(false);
}
}
}
作者

Nick Lin

發表於

2017-04-20

更新於

2023-01-18

許可協議


評論