前回の記事で、リストを表示できる「習慣トラッカー」の見た目が完成しました。
しかし今のままでは、アプリを再起動するとデータが全部消えてしまいます。
データをクラウド(次回解説するFirestore)に保存するためには、まず「これ誰のデータ?」を特定する必要があります。
つまり、ログイン機能(Authentication)の実装です。
今回は、以前Webアプリ編で解説したFirebase Authenticationを使って、AndroidアプリにGoogleログイン機能を組み込みます。
1. Webとモバイル、認証の仕組みはどう違う?
基本原理は同じですが、実装のアプローチが少し違います。
- Web (Next.js): APIキーを裏側(環境変数)に隠して使いました。
- モバイル (Android): アプリ自体がユーザーの端末にダウンロードされるため、APIキーを完全に隠すことは不可能です。
「えっ、キーがバレたら危険じゃない?」
その通りです。だからこそモバイル開発では、「キーがバレても、そのアプリ(あなたの署名が入ったアプリ)からしか使えないようにする」という制限をかけます。
この認証に使うのが「SHA-1(シャーワン)フィンガープリント」という証明書情報です。
今回はここが一番の山場になります。
2. Android特有の「設定ファイル」たち
作業に入る前に、今回触るファイルの役割を整理しておきましょう。
| ファイル名 | 役割 |
|---|---|
android/app/google-services.json | 【最重要】Firebaseの接続情報(APIキーなど)が書かれた設定ファイル。パスポートのようなもの。 |
android/build.gradle | プロジェクト全体のビルド設定。「Googleのサービスを使うよ」と宣言する場所。 |
android/app/build.gradle | アプリ単体のビルド設定。「Googleの設定ファイルを読み込んでね」と指示する場所。 |
3. 【準備】Firebaseコンソールでの設定
Step 1: Androidアプリを追加する
- Web編で作ったFirebaseプロジェクトを開きます。
- 「アプリを追加」ボタンを押し、Androidアイコンをクリックします。
- Android パッケージ名を入力します。
※前回flutter createした時の名前(android/app/build.gradleのapplicationIdに書いてあります。例:tech.simplekits.habit_tracker)。
Step 2: SHA-1フィンガープリントを登録する
Googleログインを使う場合、ここが必須です。
VS Codeのターミナルで以下のコマンドを実行してください。
cd android
./gradlew signingReport
ずらっと文字が出ますが、Task :app:signingReport の下にある SHA1: XX:XX:XX... という文字列を探してコピーし、Firebaseコンソールの「デバッグ用の署名証明書 SHA-1」の欄に貼り付けて「アプリを登録」を押します。
Step 3: 設定ファイルを配置する
- Firebaseコンソールから
google-services.jsonをダウンロードします。 - VS Codeに戻り、ダウンロードしたファイルを
android/app/フォルダの中に置きます。
💡 Gitコミットについて:
Web開発(Next.jsなど)ではAPIキーを含むファイル(.envなど)をGitに上げるのはタブーですが、モバイルアプリ開発の google-services.json は、プライベートリポジトリにコミットするケースもあります。
基本的にモバイルアプリでは「キーを隠す」だけはなく「キーにロックを掛ける」という対策もセットで行います。
⚠️ 重要:APIキーは「隠せない」前提で守る
この google-services.json はアプリ内部に埋め込まれて配布されるため、詳しい人が解析すれば中のAPIキーを取り出すことは可能です(隠せません)。
そのため、本番リリース時には必ず以下の対策が必要になります。
- Firebaseコンソールでの設定(さっきやった作業):
「Googleログイン」を使えるようにするための許可証です。APIキー自体は守られていません。 - Google Cloud Consoleでの設定:
APIキーの設定画面で「このキーは、私のアプリ(登録したSHA-1)からしか使えない」という制限(Application Restriction)をかけます。
この「Google Cloud側での制限」をかけることで、万が一キーが漏れても、他人のアプリからは勝手に使われない(APIを蹴れない)状態になります。
※今回は開発段階なので一旦このまま進めますが、ストア公開前には必ず「APIキーの制限」を行いましょう。
4. 【実装】Flutterコードの修正
必要なパッケージ(拡張機能)をインストールします。
ターミナルで android フォルダから戻るのを忘れずに(cd ..)。
flutter pub add firebase_core firebase_auth google_sign_in
次に、lib/main.dart を大幅に書き換えます。
Web開発者がやりがちな「トークンを手動で保存する」処理は不要です。Firebase SDKが勝手にやってくれます。
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
// メイン関数を非同期(async)にしてFirebaseを初期化
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(); // これがないと動きません!
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Habit Tracker',
theme: ThemeData(
primarySwatch: Colors.indigo,
useMaterial3: true,
),
// ログイン状態を監視して画面を切り替える
home: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasData) {
// ログイン済みならリスト画面へ
return const HabitListScreen();
}
// 未ログインならログイン画面へ
return const LoginScreen();
},
),
);
}
}
// ▼ ログイン画面
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
Future<void> _signInWithGoogle() async {
try {
// 1. Googleでログインを開始
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
if (googleUser == null) return; // キャンセルされた場合
// 2. 認証情報を取得
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
// 3. Firebase用のクレデンシャルを作成
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
// 4. Firebaseにサインイン(これで完了!)
await FirebaseAuth.instance.signInWithCredential(credential);
} catch (e) {
print(e); // エラー処理は本来ちゃんとやるべき
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('習慣トラッカーへようこそ', style: TextStyle(fontSize: 20)),
const SizedBox(height: 30),
ElevatedButton.icon(
icon: const Icon(Icons.login),
label: const Text('Googleでログイン'),
onPressed: _signInWithGoogle,
),
],
),
),
);
}
}
// ▼ 習慣リスト画面(前回作成したもの+ログアウト機能)
class HabitListScreen extends StatefulWidget {
const HabitListScreen({super.key});
@override
State<HabitListScreen> createState() => _HabitListScreenState();
}
class _HabitListScreenState extends State<HabitListScreen> {
final List<String> _habits = []; // ※まだローカル変数です
@override
Widget build(BuildContext context) {
final user = FirebaseAuth.instance.currentUser; // 現在のユーザー情報
return Scaffold(
appBar: AppBar(
title: Text('${user?.displayName ?? "ゲスト"}の習慣'),
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: () async {
await FirebaseAuth.instance.signOut(); // これだけでログアウト完了
},
)
],
),
body: const Center(child: Text('リストは次回DB連携で実装します')),
// ... その他のUIコードは前回と同じなので省略 ...
);
}
}
5. 動作確認
設定ファイルをいじったので、一度アプリを完全に停止してから、再度デバッグ実行(F5)してください。
ビルドに少し時間がかかります。
- アプリが起動すると「ログイン画面」が表示されます。
- 「Googleでログイン」を押すと、エミュレーター内のGoogleアカウント選択画面が出ます。
- アカウントを選ぶと、画面が「習慣リスト」に切り替わります。
- 【ここが重要】 アプリを一度終了(タスクキル)して、もう一度起動してみてください。
ログイン画面には戻らず、いきなり「習慣リスト」が表示されるはずです。
まとめ:認証状態はSDKに任せよう
お疲れ様でした!これでアプリに「鍵」がかかりました。
ポイントは以下の通りです。
- APIキー: 隠すのではなく、SHA-1で利用元を制限する(のが本番の流儀)。
- ログイン維持:
SharedPreferencesなどに自力で保存しない。FirebaseAuth.instance.authStateChanges()を信じる。
ユーザーを特定できたので、次はいよいよ「ユーザーごとのデータ」をクラウド(Firestore)に保存します。
スマホを変えてもデータが消えないアプリまで、あと一歩です!