0%

有了登入功能後,我們可能會希望儲存用戶的資料

無論是個人資料(頭像、帳號名、電話、信箱等等)
或是其他隨時間變多的資料,像是個人貼文

每發一篇貼文,我們就要把它記錄在資料庫裡
他可能需要建立在 users 的節點之下
也可以建立在 post 節點之下
或同時在多個節點做更新

這篇文章會告訴你 Firebase Realtime Database 的一些簡單操作:

1. 寫入 set()push()

2. 讀取 on()get()

3. 更新 update()

4. 刪除 delete()

在開始前,別忘了到 firebase console 啟用 realtime database

取得 reference

每次更動資料時
需要告訴程式你想要處理資料庫中什麼位置的資料

這個位置就叫做 firebase.database.Reference
可藉由ref()定義

1
const Ref = firebase.database().ref()

ref()可傳入 path,如:

1
2
3
const user = firebase.auth().currentUser
const userRef = firebase.database().ref('/users/'+ user.uid)

對於任一個 Reference
你可以用 child() 來得到下層的 Reference
假如我們要到 /user/user.uid/hobby/

1
const userHobbyRef = userRef.child('hobby')

取得 Reference 後在後面掛上函數就能變更資料了

寫入資料

set()

最簡單的方式
我們可以用set()來寫入資料

1
2
3
4
5
6
userRef.set({
name:'Tony',
age:24
hobby:['tennis','coding','eating']
timeStamp: Date.now()
})

set()函數會直接複寫該節點的資料,包括所有子節點

push()

除此之外
我們也能用 push()
帶入參數,就會在該節點的 last space 建立資料
不帶入也可以,就不會寫入
而後回傳該處的 Ref

1
2
3
4
5
6
7
8
const postListRef = firebase.database().ref('/posts/')

//保留 Ref 以便之後使用
const newPostRef = postListRef.push()
newPostRef.set('New Post')

// 直接寫入
postListRef.push('New Post')

無論如何,push()都會產生一個key
這個 key 是依 timestamp 隨機產生的
也因此push()適合有多個用戶的 app 使用
避免同時接收多個寫入指令,造成問題

要取得key值,可使用.key

1
const newPostKey = postListRef.push().key

讀取資料

on()

要讀取資料,官方最推薦的方式是on()

1
2
3
4
5
// userRef.on(eventtype,callback(snap))

userRef.on('value', (snap)=>{
setUserEmail(JSON.stringify(snap.child('user_email').val()))
})

給予一個 Ref,on()會建立一個 listener

第一個參數'value'指的是只要這個 Ref 以下任何 value 發生變化時就會觸發 callback function (第二個參數)
其他還有

  • 'child_added'
  • 'child_changed'
  • 'child_removed'
  • 'child_moved'

第二個參數是 callback,傳入 snap
這個 snap 的模樣會是該節點變更後的資料
記得要做JSON.stringfy()
並用val()取得數值

就我嘗試的結果
on()需要放在 useEffect() 裡面

get()

如果你不需要監聽,只需取得資料一次
可以使用get()

1
2
3
4
userRef.get()
.then()
.catch()
// return a Promise (dataSnapshot or rejection)

根據官方說法,過多的get()會導致效能問題
因此確保你只在需要的地方使用get()
on()才是預設選項

更新資料

update()

update()函數讓你能以一次呼叫,更改資料庫的多個地方
第一個參數是object
其中的 child property 可以填入 simple property

1
2
3
4
var adaNameRef = firebase.database().ref('users/ada/name');
// Modify the 'first' and 'last' properties, but leave other data at
// adaNameRef unchanged.
adaNameRef.update({ first: 'Ada', last: 'Lovelace' });

或是 path

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var ref = firebase.database().ref()
// Generate a new push ID for the new post
var newPostRef = ref.child("posts").push();
var newPostKey = newPostRef.key();
// Create the data we want to update
var updatedUserData = {};
updatedUserData["user/posts/" + newPostKey] = true;
updatedUserData["posts/" + newPostKey] = {
title: "New Post",
content: "Here is my new post!"
};
// Do a deep-path update
ref.update(updatedUserData, function(error) {
if (error) {
console.log("Error updating data:", error);
}
});

第二個參數是 callback 並帶入 error

參考 Multi-location updates

刪除資料

delete()

1
2
Ref.Remove()
// return a Promise

這篇文章會告訴你如何在 react native 利用 firebase 進行 Facebook 登入

主要可以分成三個步驟:

  1. 設定 facebook API
  2. 連結 Expo 和 facebook
  3. 連結 Firebase

一、設定 facebook API

1. 安裝 Expo 的 expo-facebook

1
expo install expo-facebook

2. 到 Facebook for Developers 註冊並建立應用程式

我們要的是facebook登入,所以選「打造互聯體驗」

輸入名稱和email

3. 設立 → 基本資料 → 新增平台

4. 選擇 iOS 和 Android

5.

在 ios 套件組合編號輸入 host.exp.Exponent

在 android 金鑰輸入 rRW++LUjmZZ+58EbN5DVhGAnkX4=

→ 儲存變更

糟糕為了放一些圖結果文章變得好長XD

趕快進到下個步驟

二、連結 Expo 和 facebook

1. 到 App 中定義 onlogIn()

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
async function onLogIn() {
try {
await Facebook.initializeAsync({
appId: YOUR_APP_ID, // 換成你的AppId
});

//{type,token,expirationDate,permissions,declinedPermissions,}
const result = await Facebook.logInWithReadPermissionsAsync({
permissions: ['public_profile', 'email'], // 還有其他 permission
});
if (result.type === 'success') {
// 利用 FB 提供的圖形API fetch 用戶名
const response = await fetch(`https://graph.facebook.com/me?access_token=${result.token}`);
alert( await response.json().name)
//setToken(result.token)
//在這把token存起來,有時會在onLogIn外面遇到

console.log('Logged in!');
} else {
console.log('type = cancel')
}
} catch ({ message }) {
alert(`Facebook Login Error: ${message}`);
}
}

Firebase提供的方法有些部分不適用Expo
所以這步驟與官網相比有更改一些變數
像是把Facebook.logInWithReadPersissionsAsync()回傳的值設成result

完成後可以先 run 看看
在執行 onLogIn()
會跳出 popup 問你要登入哪個 fb 帳戶
如果手機本身已經有登入 fb 帳號的話
就會直接跳出是否提供權限的視窗,按同意
但我們還沒有定義在 firebase 註冊的方法,所以還沒辦法登入

三、連結 Firebase

1. 到 firebase console 開啟 facebook sign in 的功能

appId 和 appKey 可以在 facebook API 頁面找到

2. 定義 checkLoginState()

1
2
3
4
5
6
7
8
9
10
11
12
13
function checkLoginState(response) {
let unsubscribe = firebase.auth().onAuthStateChanged((firebaseUser) => {
unsubscribe();
if (!isUserEqual(response, firebaseUser)) {
let credential = firebase.auth.FacebookAuthProvider.credential(
response.token);
firebase.auth().signInWithCredential(credential)
.catch((error) => { });
} else {
console.log('User is already signed-in Firebase with the correct user.')
}
});
}

Firebase 提供的方法有些部分不適用 Expo
所以這步驟有更改一些變數
像是刪除response.authResponse的段落
以及將response.authResponse.accessToken修改成response.token

3. 定義 isUserEqual()

1
2
3
4
5
6
7
8
9
10
11
12
13
function isUserEqual(facebookAuthResponse, firebaseUser) {
if (firebaseUser) {
var providerData = firebaseUser.providerData;
for (var i = 0; i < providerData.length; i++) {
if (providerData[i].providerId === firebase.auth.FacebookAuthProvider.PROVIDER_ID &&
providerData[i].uid === facebookAuthResponse.userID) {
// We don't need to re-auth the Firebase connection.
return true;
}
}
}
return false;
}

最後在onLogIn()裡面呼叫checkLoginState()
並傳入 result 便完成登入

小插曲

最後用 facebook 登入時遇到一個問題
如果這個用戶之前有用其他登入方式 (如email) 註冊過的話
又正好他也用同個 email 來註冊 facebook
這樣當他用 facebook 註冊 app 時就會報錯

這時有幾個解決方案:

1. 要求用該方式登入

fetchSignInMethodsForEmail() 找到當初用這個 email 登入的方法,然後跳出視窗請用戶改用此方法登入。

2. 讓他創建新帳號 獨立運作

Firebase console -> Sign-in Method -> 進階
允許用相同 email 建立不同帳戶

讓用不同管道登入的相同帳號獨立運作
但這有時不太好,因為登入最初的用意就是針對每個真實用戶
如果一個用戶能有多個帳號,有點違背初衷

3. 讓他創建新帳號 再合併

與上個步驟相同,但在用戶登入後請他合併帳號
可用 linkWithCredential()

Facebook 還有提供 圖形API
聽說功能強大
之後有機會學一學 再整理上來

繼之前介紹 Firebase 的 Email 登入和匿名登入後

這篇要示範的是 Google 登入

主要可以分成下列幾個環節:

  • 開啟 google API

  • 使用 Expo 的 Google Auth SDK

  • 使用 Firebase 的登入流程

個人覺得有點小複雜,原因正如我之前說的
Firebase 和 google 之間多了 Expo 的交接
需要一些 firebase 官網沒有提供的環節

像是 user 物件的內容就不一樣
我需要兩邊 doc 來回查才知道到底是怎樣

這真的要記錄下來
不然我大概一下就忘了

一、開啟 google API

1. 首先到 google 的 credentials page

2. 建立專案

 

這邊無法選擇內部,因此放心選外部
後續還有一些步驟但不成問題

3. 回到憑證頁面 -> 建立憑證 -> 選 OAuth 用戶端 ID

4. 依照手機系統選擇 Android / iOS

5. 設定套件名稱&憑證指紋

  • 套件名稱填 host.exp.exponent
  • 在 terminal 中 run openssl rand -base64 32 | openssl sha1 -c 並將 output 填入 憑證指紋

二、使用 Expo 的 Google Auth SDK

expo install expo-google-app-auth

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import * as Google from 'expo-google-app-auth';

async function signInWithGoogleAsync() {
try {
const result = await Google.logInAsync({
androidClientId: YOUR_CLIENT_ID_HERE, //在這裡貼上你的用戶端編號, 記得加引號
// iosClientId: YOUR_CLIENT_ID_HERE,
scopes: ['profile', 'email'],
});

if (result.type === 'success') {
return result.accessToken;
} else {
return { cancelled: true };
}
} catch (e) {
return { error: true };
}
}

用戶端編號可以從 Google API 的憑證頁面中找到

三、使用 Firebase 的登入流程

firebase 提供的 popup/redirect 兩種方法
只能用在 web 上,在手機上會報錯:

Error: This operation is not supported in the environment this application is running on. "location.protocol" must be http, https or chrome-extension and web storage must be enabled.

所以我們在行動端只能用進階做法

1. 到 firebase console 開啟 Google sign in 的功能

appId 和 appKey 可以在 Google API 頁面找到

2. 定義 onSignIn()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function onSignIn(googleUser) {
var unsubscribe = firebase.auth().onAuthStateChanged((firebaseUser) => {
unsubscribe();
// 似乎是listener的常用語法,藉由呼叫函式本身,讓後面的程式只運行一次就好
if (!isUserEqual(googleUser, firebaseUser)) {
var credential = firebase.auth.GoogleAuthProvider.credential(
googleUser.idToken,
googleUser.accessToken
);

firebase.auth().signInWithCredential(credential)
.catch((error) => { alert('onSignInCredential'+error) });
} else {
console.log('User already signed-in Firebase.');
}
});
}
  • 為什麼 unsubscribe 裡面還有 unsubscribe? link

  • GoogleAuthProvider.credential()有兩個input,idTokenaccessToken,官網給的寫法是googleUser.getAuthResponse().id_token,但因為我們用的是 Expo Google.logInAsync回傳的googleUser,他不含任何函式,自然就沒有getAuthResponse()可以用。 link

3. 定義 isUserEqual()

1
2
3
4
5
6
7
8
9
10
11
12
13
function isUserEqual(googleUser, firebaseUser) {
if (firebaseUser) {
var providerData = firebaseUser.providerData;
for (var i = 0; i < providerData.length; i++) {
if (providerData[i].providerId === firebase.auth.GoogleAuthProvider.PROVIDER_ID &&
providerData[i].uid === googleUser.user.id) {
// We don't need to reauth the Firebase connection.
return true;
}
}
}
return false;
}
  • 這邊的 googleUser.user.id 也是上述的原因

4. 把onSignIn()放進 signInWithGoogleAsync() 裡並傳入 user 物件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function signInWithGoogleAsync() {
try {
const result = await Google.logInAsync({
androidClientId: '994735805040-7q2nlrcnma0mssgvs7fimrmdp6dvuaqk.apps.googleusercontent.com', //在這裡貼上你的用戶端編號
// iosClientId: YOUR_CLIENT_ID_HERE,
scopes: ['profile', 'email'],
});

if (result.type === 'success') {
onSignIn(result) // <---- HERE
return result.accessToken;
} else { return { cancelled: true } }
} catch (e) { return { error: true } }
}

完成! 可以到這裡參考我的範例