Firebase 연동 — Auth, Firestore, Cloud Messaging
Firebase 연동 — Auth, Firestore, Cloud Messaging
Firebase는 Google이 제공하는 BaaS(Backend as a Service)로, Flutter와의 통합이 매우 잘 되어 있습니다. 인증, 데이터베이스, 푸시 알림을 빠르게 구현할 수 있습니다.
Firebase 초기 설정
FlutterFire CLI 설치
# FlutterFire CLI 설치
dart pub global activate flutterfire_cli
# Firebase 프로젝트 연결 (자동 설정)
flutterfire configure
이 명령어가 firebase_options.dart 파일을 자동 생성합니다.
패키지 설치
dependencies:
firebase_core: ^3.0.0
firebase_auth: ^5.0.0
cloud_firestore: ^5.0.0
firebase_messaging: ^15.0.0
초기화
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
Firebase Auth — 인증
이메일/비밀번호 로그인
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// 현재 유저
User? get currentUser => _auth.currentUser;
// 인증 상태 스트림
Stream<User?> get authStateChanges => _auth.authStateChanges();
// 회원가입
Future<UserCredential> signUp(String email, String password) async {
try {
return await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
throw _handleAuthError(e);
}
}
// 로그인
Future<UserCredential> signIn(String email, String password) async {
try {
return await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
throw _handleAuthError(e);
}
}
// 로그아웃
Future<void> signOut() async {
await _auth.signOut();
}
// 에러 처리
String _handleAuthError(FirebaseAuthException e) {
switch (e.code) {
case 'email-already-in-use':
return '이미 등록된 이메일입니다';
case 'wrong-password':
return '비밀번호가 틀렸습니다';
case 'user-not-found':
return '등록되지 않은 이메일입니다';
case 'weak-password':
return '비밀번호가 너무 약합니다';
default:
return '인증 에러: ${e.message}';
}
}
}
인증 상태 감시
class AuthWrapper extends StatelessWidget {
const AuthWrapper({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasData) {
return const HomeScreen(); // 로그인 상태
}
return const LoginScreen(); // 비로그인 상태
},
);
}
}
Firestore — NoSQL 데이터베이스
CRUD 작업
import 'package:cloud_firestore/cloud_firestore.dart';
class PostRepository {
final _collection = FirebaseFirestore.instance.collection('posts');
// 생성
Future<void> create(Post post) async {
await _collection.add({
'title': post.title,
'body': post.body,
'authorId': post.authorId,
'createdAt': FieldValue.serverTimestamp(),
});
}
// 조회 (1회)
Future<List<Post>> getAll() async {
final snapshot = await _collection
.orderBy('createdAt', descending: true)
.limit(20)
.get();
return snapshot.docs.map((doc) {
return Post.fromFirestore(doc.id, doc.data());
}).toList();
}
// 실시간 조회 (스트림)
Stream<List<Post>> watchAll() {
return _collection
.orderBy('createdAt', descending: true)
.snapshots()
.map((snapshot) {
return snapshot.docs.map((doc) {
return Post.fromFirestore(doc.id, doc.data());
}).toList();
});
}
// 수정
Future<void> update(String id, Map<String, dynamic> data) async {
await _collection.doc(id).update({
...data,
'updatedAt': FieldValue.serverTimestamp(),
});
}
// 삭제
Future<void> delete(String id) async {
await _collection.doc(id).delete();
}
// 조건 조회
Future<List<Post>> getByAuthor(String authorId) async {
final snapshot = await _collection
.where('authorId', isEqualTo: authorId)
.orderBy('createdAt', descending: true)
.get();
return snapshot.docs.map((doc) {
return Post.fromFirestore(doc.id, doc.data());
}).toList();
}
}
실시간 데이터로 UI 업데이트
StreamBuilder<List<Post>>(
stream: PostRepository().watchAll(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('에러: ${snapshot.error}');
}
if (!snapshot.hasData) {
return const CircularProgressIndicator();
}
final posts = snapshot.data!;
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
return ListTile(title: Text(posts[index].title));
},
);
},
)
Firebase Cloud Messaging — 푸시 알림
import 'package:firebase_messaging/firebase_messaging.dart';
class PushNotificationService {
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
Future<void> initialize() async {
// 권한 요청 (iOS)
final settings = await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
print('알림 권한: ${settings.authorizationStatus}');
// FCM 토큰 가져오기
final token = await _messaging.getToken();
print('FCM 토큰: $token');
// 토큰 갱신 감시
_messaging.onTokenRefresh.listen((newToken) {
// 서버에 새 토큰 전송
print('토큰 갱신: $newToken');
});
// 포그라운드 메시지 처리
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('포그라운드 메시지: ${message.notification?.title}');
// 로컬 알림 표시
});
// 알림 클릭으로 앱 열었을 때
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('알림 클릭: ${message.data}');
// 해당 화면으로 이동
});
}
}
// 백그라운드 메시지 핸들러 (최상위 함수)
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(
RemoteMessage message,
) async {
await Firebase.initializeApp();
print('백그라운드 메시지: ${message.messageId}');
}
// main에서 등록
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(
_firebaseMessagingBackgroundHandler,
);
runApp(const MyApp());
}
Firestore 보안 규칙
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// 인증된 사용자만 읽기/쓰기
match /posts/{postId} {
allow read: if request.auth != null;
allow write: if request.auth != null
&& request.auth.uid == resource.data.authorId;
}
}
}
면접 포인트: Firestore 보안 규칙을 클라이언트 측에서 검증하지 않고 서버 측(Firebase Rules)에서 검증해야 합니다. 클라이언트 코드는 우회 가능하기 때문입니다.
정리
flutterfire configure로 Firebase 연동을 자동 설정합니다- Firebase Auth의
authStateChanges()스트림으로 인증 상태를 감시합니다 - Firestore의
snapshots()로 실시간 데이터 동기화가 가능합니다 - FCM 토큰을 서버에 전송해서 푸시 알림을 보낼 수 있습니다
- 보안 규칙을 반드시 설정하여 데이터 접근을 제어하세요
댓글 로딩 중...