この記事で読むべき対象者
- EXPO(React-Native)とfirebaseでアプリを作成している
- 過去にログインしているのにauth.currentUserの中身がnullで返ってきてしまう
- onAuthStateChangedによる自動ログイン機能がfirebase ver9にアップデートしたら機能しなくなった
遭遇した経緯
「てがきdeよせがき」でEXPO SDK45を使用していましたがシミュレーターで動かなくなってしまったため、EXPO SDK48にアップデートを行いました。するとreact-native coreからasync-storageが削除されてしまいfirebase ver8のままでは正常に動かなくなってしまいました。firebase ver9にアップデートしたところonAuthStateChangeで実装していた自動ログイン機能が正常に働かなくなってしまい、アプリを再起動するたびにログアウトしてしまう現象が現れました。ただメールとパスワードによるログインは問題なくできるし、firestoreからドキュメントの取得やstorageから画像の取得も正常で寄せ書きの投稿にも支障がありませんでした。
リファレンスを読むだけでは必ず罠にハマる
当然ですがfirebase ver8からver9にアップデートしたのでコードを書き換えないといけません。
そこで公式のリファレンスを読んで書き換えてみます。
import React, { useEffect } from "react";
import { initializeApp } from "firebase/app";
import {
getAuth,
onAuthStateChanged,
signInAnonymously
} from "firebase/auth";
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: "",
};
const firebaseApp = initializeApp(firebaseConfig);
const auth = getAuth(firebaseApp)
export default function App() {
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
// ユーザーがログインしている場合自動ログインする
dispatch(
setUser({
value: {
isAnonymous: user.isAnonymous,
phoneNumber: user.phoneNumber,
photoURL: user.photoURL,
email: user.email,
uid: user.uid,
emailVerified: user.emailVerified,
},
})
);
} else {
//ユーザーがログインしていない場合の処理
signInAnonymously(auth)
.then(() => {
console.log("signInAnonymously");
})
.catch(() => {
Alert.alert("エラー", "アプリを再起動してください");
});
}
unsubscribe();
});
return unsubscribe;
}, []);
しかし上記のコードはアプリ起動時にonAuthStateChangedが働くものの、過去にログインしていたとしても勝手にログアウト状態になってしまいログインしていない場合の処理が実行されてしまいます。
つまり過去に匿名ユーザー(A)でログインしていたら、アプリ再起動時にまた匿名ユーザー(A)として自動的にログインしてほしいのに、匿名ユーザー(B)が作られてしまうようになってしまいました。
これの問題点は匿名ユーザー(A)で作った寄せ書きの台紙はアプリを再起動すると匿名ユーザー(B)となるため寄せ書きの閲覧が永久に出来なくなってしまいます。。
よってこの問題をのこしたままでは今後アプリのアップデートが出来ません。早急に解決へとりかかりました。
自動ログインがうまくいかない理由
ここでこの記事のタイトルに戻ります。
Authenticationの初期化に失敗しているから…
公式リファレンスにはこんなことが書かれています。
注: currentUser が null になる原因としては、Auth オブジェクトの初期化が完了していないことも考えられます。オブザーバーを使用してユーザーのログイン ステータスを追跡している場合は、この状況に対処する必要はありません。
https://firebase.google.com/docs/auth/web/manage-users?hl=ja#get_the_currently_signed-in_user
実際私も auth.currentUser の中身をみてみると null が返ってきていました。
Authenticationの初期化に失敗する原因
じつはコレも記事の冒頭に書いてあります。
react-native coreからasync-storageが削除されてしまったからです
本来のfirebaseは過去のログイン情報をLocalStorageに保存されて永続化されますが、react-nativeの場合はasync-storageに保存される仕組みだったため保存先がなくなってしまったんです。
Warning: Async Storage has been extracted from react-native core
and will be removed in a future release. It can now be installed
and imported from '@react-native-community/async-storage'
instead of 'react-native'. See https://github.com/react-native-community/react-native-async-storage
firebase ver8を使っていたときは上記のwarningが出てましたが、firebaseのリリースノートによるとfirebase 9.6.5からは表示されなくなってしまったようです。
Authenticationの初期化に失敗する原因のヒントが全くない状態でここまでたどりつかないといけません!
開発者が非推奨バージョンを提供している場合、SDK により、React Native が AsyncStorage の非推奨に関する警告をログに記録しなくなりました。 Github の問題 #1847 を参照してください。
(Google翻訳にて和訳)
https://firebase.google.com/support/release-notes/js#version_965_-_january_27_2022
このwarning残しておかないと絶対罠にハマるでしょ…
対処法
対処法はGithubのissueにあるこの記述を参考にさせてもらいました。(もはや答えが書いてある…)
これは、アプリで initializeAuth を呼び出す完全なファイルで、次のようになります。
(Google翻訳で和訳 一部抜粋)
https://github.com/firebase/firebase-js-sdk/issues/6231#issuecomment-1147831245import { initializeAuth } from 'firebase/auth'; import { getReactNativePersistence } from 'firebase/auth/react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; export const afterFirebaseInitialize = firebaseApp => { initializeAuth(firebaseApp, { persistence: getReactNativePersistence(AsyncStorage), }); };
まずは@react-native-async-storage/async-storageをインストールします
npm install @react-native-async-storage/async-storage
あとは下記のようにimportを追加しauthの初期化を書き換えればOK!
import React, { useEffect } from "react";
import { initializeApp } from "firebase/app";
import {
onAuthStateChanged,
signInAnonymously
} from "firebase/auth";
//↓のimportを追加する
import {
getReactNativePersistence,
initializeAuth,
} from "firebase/auth/react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: "",
};
const firebaseApp = initializeApp(firebaseConfig);
//↓のように記述することでreact-native専用にauthが初期化され,
//新しくインストールしたAsyncStorageに認証情報が保存され永続化します。
const auth = initializeAuth(firebaseApp, {
persistence: getReactNativePersistence(AsyncStorage),
});
export default function App() {
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
// ユーザーがログインしている場合自動ログインする
dispatch(
setUser({
value: {
isAnonymous: user.isAnonymous,
phoneNumber: user.phoneNumber,
photoURL: user.photoURL,
email: user.email,
uid: user.uid,
emailVerified: user.emailVerified,
},
})
);
} else {
//ユーザーがログインしていない場合の処理
signInAnonymously(auth)
.then(() => {
console.log("signInAnonymously");
})
.catch(() => {
Alert.alert("エラー", "アプリを再起動してください");
});
}
unsubscribe();
});
return unsubscribe;
}, []);
- getAuth()
デフォルト設定でauthの初期化を行う、通常であればgetAuth()を使えば問題なく動作する
- initializeAuth()
authの初期化の際により細かい設定が可能となる。この記事ではログイン情報の永続化のため保存先に"@react-native-async-storage/async-storage"からimportしたAsyncStorageを指定している。
まとめ
私はこの自動ログインに関わるバグを解決まで1週間を要してしまいました。
Githubの記述を見つけてからは早かったんですけどね…
firebase リファレンスを読むだけじゃココまでたどり着くのは難しいと思います…
この記事が同じ悩みを持ってしまった方の早期解決に役立つことを祈っております😆😆😆