【Flutter】Cloud Functionsを使わずに、Firebase AuthとLINE SDKを使ってLINEログインを実装する
まえがき
先日↑こんな記事を書いた。
Flutter×Firebaseのアプリに、LINEログインを追加するという内容だが、Clond Functionsも必要で、個人的にはちょっと面倒というか、アプリ側の実装だけで済ませたいなという感じだった。
そこで今回は、Cloud Functionsは使わずに、LINEログインを実装する方法について書いてみようと思う。
どうやるか?
ざっくりのイメージだが、前回は↓こんな感じ。
ログイン状態をFirebase Authで一元管理するために、LINEログインで得られた情報(userId)を使って、Firebase Authでもログイン(カスタム認証)するという手順を踏んでいた。
前回の記事でも書いたが、この方法だと
Firebase Authでもログイン(カスタム認証)
これがアプリ側ではできないので、Cloud Functionsを使わざるを得なかった。
そのため今回は、Firebase Authでのログイン状態の一元管理をやめ、Firebase AuthとLINE SDKの両方でログイン状態を管理する
というやり方に切り替える。
雑な図だが↓こんな感じ(矢印の向きは正直適当・・・)
なので厳密にいうと、本記事タイトルの「Cloud Functionsを使わずに、Firebase AuthとLINE SDKを使ってLINEログインを実装する」というのは間違いで、LINEログイン周りはLINE SDKに一任する形になる。
アプリ側の実装
前提を整理したので、実際の実装について書いていく。
ファイル構成
ファイル構成は以下の通り(前回と同じ)。
lib/
├ main.dart
└ app/
├ sign_in/
│ └ sign_in_page.dart (View)
│
└ auth_manager/
└ auth_manager.dart (ロジック)
以下、それぞれのファイルの中身を書いていく。
main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_line_sdk/flutter_line_sdk.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'app/auth_manager/auth_manager.dart';
import 'app/sign_in/sign_in_page.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await LineSDK.instance.setup('ここはLINEのチャンネルID');
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends ConsumerWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final _authManager = ref.watch(authManagerProvider);
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'TITLE',
home: _authManager.isLoggedIn
? const HomePage() // ログイン後の画面
: const SignInPage(),
);
}
}
前回同様、状態管理はriverpodを使用している。
authManager.isLoggedIn
でログイン状態を判定して、ログイン後ならHomePage
(仮)を、未ログインならSignInPage
を表示する。
sign_in_page.dart
ログイン画面は至ってシンプルで、ログインボタンがあって、ボタンを押したらログイン処理が実行されるだけ。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../auth_manager/auth_manager.dart';
class SignInPage extends ConsumerWidget {
const SignInPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () async =>
await ref.read(authManagerProvider).signInWithLine(),
child: const Text('Sign In with LINE'),
),
),
);
}
}
auth_manager.dart
では一番重要な、auth_manager.dart
はというと・・・
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_line_sdk/flutter_line_sdk.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final authManagerProvider = ChangeNotifierProvider<AuthManager>(
(ref) {
return AuthManager();
},
);
class AuthManager with ChangeNotifier {
AuthManager() {
// Firebase Authのログイン状態を管理
_firebaseAuth.authStateChanges().listen((user) {
isLoggedIn = user != null;
currentUserId = user?.uid;
notifyListeners();
});
Future<void>(
() async => await _checkLineAuthState(),
);
}
final _firebaseAuth = FirebaseAuth.instance;
final _lineSdk = LineSDK.instance;
String? currentUserId;
bool isLoggedIn = false;
// ログイン/ログアウト時に、LINE SDKのログイン状態を更新する処理
Future<void> _checkLineAuthState() async {
final _lineAccessToken = await _lineSdk.currentAccessToken;
isLoggedIn = _lineAccessToken != null;
if (isLoggedIn) {
final _lineUser = await _lineSdk.getProfile();
currentUserId = _lineUser.userId;
}
notifyListeners();
}
Future<void> signInWithFirebaseAuth() async {
// Firebase対応のログイン処理
}
Future<void> signInWithLine() async {
// LINEログイン
await _lineSdk.login();
await _checkLineAuthState();
}
// ログアウト処理
Future<void> signOut() async {
// Firebase, LINEどちらも、未ログイン状態でsignOut()しても問題なし
await _firebaseAuth.signOut();
await _lineAuthService.signOut();
await _checkLineAuthState();
}
}
Firebase Authについては、authStateChanges()
を使用すれば、streamでログイン状態を監視できるので、ログイン状態の管理は比較的シンプル。
対してLINE SDKには同じようなメソッドが用意されていないので、ログイン/ログアウトの際に、明示的にログイン状態を更新してやる必要がある。
LINE SDKのログイン状態の判別については、他にもやり方があるかもしれないが、ひとまず今回は、
final _lineAccessToken = await _lineSdk.currentAccessToken;
isLoggedIn = _lineAccessToken != null;
というように、currentAccessTokenの有無で判別している。
また、ログイン処理とは直接関係ないが、ログイン中のuserのuserIdは使用する機会があるので、↓のように、ログインした場合はcurrentUserIdを保持するようにしている。
if (isLoggedIn) {
final _lineUser = await _lineSdk.getProfile();
currentUserId = _lineUser.userId;
}
これで完成・・・??
ひとまずここまでの内容で、Cloud Functionsを使わずにログイン処理を実装することができた。
auth_manager.dart
内はちょっとごちゃっとしているが、外側(View)から見れば、_authManager.isLoggedIn
でログイン状態を判別できるのでまあ悪くはないと思う。
でもやっぱり、auth_manager.dart
のごちゃごちゃが気になる。
Firebase Authで一元管理していた時の方がスッキリしたので、少しでもそれに近づけたい。
ということで、もうちょっとリファクタリングしてみる。
LINE SDKのログイン管理部分を、別ファイルに切り出す
↓こんな感じで、LINE関連の処理をline_auth_service.dart
に切り出す
lib/
├ main.dart
└ app/
├ auth_manager/
│ └ auth_manager.dart
│
└ line_auth/
└ line_auth_service.dart NEW!!
line_auth_service.dart
ただ切り出すだけでなく、Firebase Auth同様に、streamでログイン状態を監視できるようにする。
こんな感じ↓
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_line_sdk/flutter_line_sdk.dart';
final lineAuthServiceProvider = Provider<LineAuthService>((ref) {
return LineAuthService();
});
class LineAuthService {
LineAuthService() {
Future<void>(() async {
try {
final _user = await _lineSdk.getProfile();
_authStateController.sink.add(_user.userId);
} catch (e) {
// 未ログイン時はgetProfile()がエラーになる
print('Not Logged In');
}
});
}
final _lineSdk = LineSDK.instance;
final _authStateController = StreamController<String?>();
// ログインしていれば、userIdを返す
Stream<String?> get authStateChange => _authStateController.stream;
Future<void> signIn() async {
await _lineSdk.login();
final _user = await _lineSdk.getProfile();
_authStateController.sink.add(_user.userId);
}
Future<void> signOut() async {
await _lineSdk.logout();
_authStateController.sink.add(null);
}
}
authStateChange
という名前は微妙な気もするが、とりあえずFirebase Authに合わせた。
Firebase Authの場合は、streamでUser
を取得できるが、今のところ使用するのはuserIdのみなので、今回はString
にしている。
auth_manager.dart
auth_manager.dart
は以下のようになった。
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../line_auth/line_auth_service.dart';
final authManagerProvider = ChangeNotifierProvider<AuthManager>(
(ref) {
return AuthManager(
ref.watch(lineAuthServiceProvider),
);
},
);
class AuthManager with ChangeNotifier {
AuthManager(this._lineAuthService) {
// Firebase Authのログイン状態を管理
_firebaseAuth.authStateChanges().listen((user) {
isLoggedIn = user != null;
currentUserId = user?.uid;
notifyListeners();
});
// LINE SDKのログイン状態を管理
_lineAuthService.authStateChange.listen((userId) {
isLoggedIn = userId != null;
currentUserId = userId;
notifyListeners();
});
}
final LineAuthService _lineAuthService;
final _firebaseAuth = FirebaseAuth.instance;
String? currentUserId;
bool isLoggedIn = false;
Future<void> signInWithFirebaseAuth() async {
// Firebase対応のログイン処理
}
// LINEログイン
Future<void> signInWithLine() async => await _lineAuthService.signIn();
// ログアウト処理
Future<void> signOut() async {
// Firebase, LINEどちらも、未ログイン状態でsignOut()しても問題なし
await _firebaseAuth.signOut();
await _lineAuthService.signOut();
}
}
Firebase AuthとLINE SDK、それぞれのログイン状態の管理がほぼ同じコードで実現でき、さっきよりシンプルになったと思う。
firebase_auth_service.dart
も作って、authStateChanges()
で同じくStringのuserIdを返すようにすれば、auth_manager.dart
自体はもっとスッキリ書けそうだが、今回はここまでにしておく。
さいごに
以上のやり方で、Cloud Functionsを使うことなく、Firebase AuthとLINE SDKを併用したログイン機能を実装することができた。
どちらの方法を使うかは正直好みの気もするが、Cloud Functionsのメンテナンスやサーバー代を考慮すると、今回の方法の方がいいかもしれない。
Discussion