
flutter 강의를 수강하며, 마지막 부분에 미니프로젝트 용도로 firebase와 프로그램 연동하는 부분 보고 작성하는 글.
들어야지 라고 결심한게 작년 가을의 일이었는데, 현생이슈 체력 이슈 등등..
이제서야 마무리 짓게 되었다.
Firebase에 대한 소개
Firebase는 구글에서 제공하는 강력한 모바일 및 웹 애플리케이션 개발 플랫폼이다.
서버 인프라를 직접 구축하거나 관리하지 않고도,
다양한 백엔드 기능과 서비스를 손쉽게 통합할 수 있도록 도와주는 강력한 툴이다.
데이터베이스/서버 유지보수가 쉬워지는 만큼,
개발자는 앱의 핵심 기능과 사용자 경험 개선에 더욱 집중할 수 있을 것으로 보인다.
1. Firebase란?
Firebase는 원래 2011년 Firebase, Inc.에서 시작된 실시간 데이터 동기화 기술을 기반으로 한 서비스였다.
이후 2014년에 구글에 인수되면서 구글의 다양한 클라우드 서비스와 통합되어 더욱 확장된 백엔드 플랫폼으로 발전했다.
현재 Firebase는 모바일과 웹 애플리케이션 개발에 필요한 다양한 도구를 한 곳에서 제공하며,
빠른 개발 및 배포, 효율적인 운영을 지원한다.
2. Firebase의 주요 기능
2.1 실시간 데이터베이스 (Realtime Database)
특징: NoSQL 클라우드 데이터베이스로, JSON 형식의 데이터를 실시간으로 동기화한다.
장점: 여러 클라이언트 간에 데이터가 즉시 반영되어 협업 및 실시간 애플리케이션에 적합
2.2 Cloud Firestore
특징: 문서(document)와 컬렉션(collection) 기반의 NoSQL 데이터베이스로, 복잡한 쿼리와 확장성이 우수하다.
장점: 보다 유연한 데이터 구조와 풍부한 질의 기능을 제공하여 대규모 데이터 처리에 유리하다.
2.3 사용자 인증 (Authentication)
특징: 이메일/비밀번호, 전화번호, 소셜 로그인(Google, Facebook, Twitter 등)과 같은 다양한 인증 방식을 지원한다.
장점: 보안이 강화된 사용자 관리 시스템을 손쉽게 구현할 수 있습니다.
2.4 클라우드 스토리지 (Cloud Storage)
특징: 이미지, 동영상, 오디오 파일 등 대용량 데이터를 안전하게 저장하고 관리할 수 있는 서비스.
장점: 확장성과 안정성이 뛰어나며, 파일 업로드 및 다운로드가 간편하다.
2.5 클라우드 함수 (Cloud Functions)
특징: 서버리스 컴퓨팅 환경에서 이벤트 기반 백엔드 코드를 실행할 수 있다.
장점: 서버 관리 없이 필요한 로직을 실행할 수 있어 개발과 유지보수가 용이하다.
2.6 호스팅 (Hosting)
특징: 정적 웹사이트를 전 세계에 빠르고 안전하게 배포할 수 있는 서비스를 제공한다.
장점: SSL 인증서 자동 제공 및 간편한 배포 프로세스로 안정적인 웹사이트 운영이 가능하다.
2.7 기타 서비스
분석(Analytics): 사용자 행동 및 앱 성능 데이터를 실시간으로 추적하고 분석할 수 있다.
Crashlytics 및 Performance Monitoring: 앱 충돌과 성능 문제를 모니터링하여 신속하게 대응할 수 있다.
3. Firebase의 장점과 활용 사례
3.1 개발 생산성 향상
서버 구축, 데이터베이스 관리, 사용자 인증 등의 반복적이고 복잡한 작업을 대신 처리해 줌으로써 개발 시간을 단축시킨다. 스타트업이나 소규모 프로젝트에서도 초기 투자 비용과 운영 부담을 크게 줄일 수 있다.
-> 그냥 혼자 프로젝트 가볍게 만들어보는 학생들/1인개발자들한테도 무료 요금제가 있어 괜찮다.
3.2 실시간 데이터 동기화
실시간 데이터베이스와 Firestore를 통해 여러 사용자 간의 데이터 동기화가 즉각적으로 이루어져, 채팅 앱, 협업 툴, 실시간 게임 등에서 탁월한 성능을 발휘한다.
3.3 확장성과 유연성
Firebase의 서비스는 클라우드 기반으로 동작하기 때문에, 사용자 수가 급격히 증가하더라도 자동으로 확장된다.
또한, 다양한 플랫폼(iOS, Android, 웹 등)을 지원하여 크로스 플랫폼 애플리케이션 개발에도 유리하다.
3.4 간단한 배포와 관리
Firebase Hosting과 Cloud Functions 등을 활용하면, 별도의 서버 관리 없이도 앱을 전 세계에 배포하고, 실시간 모니터링을 통해 문제 발생 시 빠르게 대응할 수 있다.
회사에서는 전통적인 oracle 데이터베이스를 통해 정규화/조직화된 데이터를 주로 만지고 있고,
당연히 SQL 잘 짜는 것/튜닝하는 것도 중요한 실무 능력 중 하나로 취급되고 있다.
또한, 인사팀/실무 리더or 조직장이 아니라 채용과정에 직접 참여하지 않긴 하지만
신입사원~저연차 주니어들과 간간이 이야기를 나눠보면
신입공채 입사 및 주니어 단계에서의 평가 시 SQLD 같은 자격증같은 게 중요한 가점 및 평가 요소가 되는 것 같아 보였다.
나 역시도 주니어 시절 사수가 SQLD 하나 따오면 서브 업무 하나 빼 준다는 식으로 (?) 딜을 치는 데 낚여 (...)
자격증을 딴 전적이 있긴 하다.
https://career-gogimandu.tistory.com/24
SQLD 자격증 취득 후기
2020년 5월에 있었던 37회 SQLD 시험을 쳐서 합격했으니, 많이 늦은 후기. tmi로 시작하자면.. 어차피 매년 인사 평가 때문에 IT자격증을 일년에 하나 이상 따야 한다 그래서 사수가 공부할겸 따보라
career-gogimandu.tistory.com
정형화된 SQL만 보다가 이런 NoSQL 쉬운 툴을 만져보니 상당히 신세계로 느껴지긴 함.
flutter로 만든 간단한 버킷리스트 앱을 firebase와 연동하였다.
1. main.dart
import 'package:bucket_list_with_firebase/auth_service.dart';
import 'package:bucket_list_with_firebase/bucket_service.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized(); // main 함수에서 async 사용하기 위함
await Firebase.initializeApp(); // firebase 앱 시작
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AuthService()),
ChangeNotifierProvider(create: (context) => BucketService()),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
User? user = context.read<AuthService>().currentUser();
return MaterialApp(
debugShowCheckedModeBanner: false,
home: user == null ? LoginPage() : HomePage(),
);
}
}
/// 로그인 페이지
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Consumer<AuthService>(
builder: (context, authService, child) {
User? user = authService.currentUser();
return Scaffold(
appBar: AppBar(title: Text("로그인")),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
/// 현재 유저 로그인 상태
Center(
child: Text(
user == null ? "로그인해 주세요 🙂" : "${user.email}님 안녕하세요 👋",
style: TextStyle(
fontSize: 24,
),
),
),
SizedBox(height: 32),
/// 이메일
TextField(
controller: emailController,
decoration: InputDecoration(hintText: "이메일"),
),
/// 비밀번호
TextField(
controller: passwordController,
obscureText: false, // 비밀번호 안보이게
decoration: InputDecoration(hintText: "비밀번호"),
),
SizedBox(height: 32),
/// 로그인 버튼
ElevatedButton(
child: Text("로그인", style: TextStyle(fontSize: 21)),
onPressed: () {
// 로그인
authService.signIn(
email: emailController.text,
password: passwordController.text,
onSuccess: () {
// 로그인 성공
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("로그인 성공"),
));
// HomePage로 이동
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomePage()),
);
},
onError: (err) {
// 에러 발생
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(err),
));
},
);
},
),
/// 회원가입 버튼
ElevatedButton(
child: Text("회원가입", style: TextStyle(fontSize: 21)),
onPressed: () {
authService.signUp(
email: emailController.text,
password: passwordController.text,
onSuccess: () {
// 회원가입 성공
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("회원가입 성공"),
));
},
onError: (err) {
// 에러 발생
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(err),
));
},
);
},
),
],
),
),
);
},
);
}
}
/// 홈페이지
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
TextEditingController jobController = TextEditingController();
@override
Widget build(BuildContext context) {
return Consumer<BucketService>(
builder: (context, bucketService, child) {
final authService = context.read<AuthService>();
User user = authService.currentUser()!;
return Scaffold(
appBar: AppBar(
title: Text("버킷 리스트"),
actions: [
TextButton(
child: Text("로그아웃"),
onPressed: () {
// 로그아웃
context.read<AuthService>().signOut();
// 로그인 페이지로 이동
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => LoginPage()),
);
},
),
],
),
body: Column(
children: [
/// 입력창
Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
/// 텍스트 입력창
Expanded(
child: TextField(
controller: jobController,
decoration: InputDecoration(
hintText: "하고 싶은 일을 입력해주세요.",
),
),
),
/// 추가 버튼
ElevatedButton(
child: Icon(Icons.add),
onPressed: () {
// create bucket
if (jobController.text.isNotEmpty) {
bucketService.create(jobController.text, user.uid);
}
},
),
],
),
),
Divider(height: 1),
/// 버킷 리스트
Expanded(
child: FutureBuilder<QuerySnapshot>(
future: bucketService.read(user.uid),
builder: (context, snapshot) {
final documents = snapshot.data?.docs ?? [];
if (documents.isEmpty) {
return Center(child: Text("버킷 리스트를 작성해주세요."));
}
return ListView.builder(
itemCount: documents.length,
itemBuilder: (context, index) {
final doc = documents[index];
String job = doc.get("job");
bool isDone = doc.get("isDone");
return ListTile(
title: Text(
job,
style: TextStyle(
fontSize: 24,
color: isDone ? Colors.grey : Colors.black,
decoration: isDone
? TextDecoration.lineThrough
: TextDecoration.none,
),
),
// 삭제 아이콘 버튼
trailing: IconButton(
icon: Icon(CupertinoIcons.delete),
onPressed: () {
// 삭제 버튼 클릭시
bucketService.delete(doc.id);
},
),
onTap: () {
// 아이템 클릭하여 isDone 업데이트
bucketService.update(doc.id, !isDone);
},
);
},
);
}),
),
],
),
);
},
);
}
}
2. bucket_service.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class BucketService extends ChangeNotifier {
final bucketCollection = FirebaseFirestore.instance.collection('bucket');
Future<QuerySnapshot> read(String uid) async {
// 내 bucketList 가져오기
return bucketCollection.where('uid', isEqualTo: uid).get();
}
void create(String job, String uid) async {
// bucket 만들기
await bucketCollection.add({
'uid': uid, // 유저 식별자
'job': job, // 하고싶은 일
'isDone': false, // 완료 여부
});
notifyListeners(); // 화면 갱신
}
void update(String docId, bool isDone) async {
// bucket isDone 업데이트
await bucketCollection.doc(docId).update({"isDone": isDone});
notifyListeners();
}
void delete(String docId) async {
// bucket 삭제
await bucketCollection.doc(docId).delete();
notifyListeners();
}
}
3. auth_service.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class AuthService extends ChangeNotifier {
User? currentUser() {
// 현재 유저(로그인 되지 않은 경우 null 반환)
return FirebaseAuth.instance.currentUser;
}
void signUp({
required String email, // 이메일
required String password, // 비밀번호
required Function() onSuccess, // 가입 성공시 호출되는 함수
required Function(String err) onError, // 에러 발생시 호출되는 함수
}) async {
// 회원가입
// 이메일 및 비밀번호 입력 여부 확인
if (email.isEmpty) {
onError("이메일을 입력해 주세요.");
return;
} else if (password.isEmpty) {
onError("비밀번호를 입력해 주세요.");
return;
}
// firebase auth 회원 가입
try {
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: email,
password: password,
);
// 성공 함수 호출
onSuccess();
} on FirebaseAuthException catch (e) {
// Firebase auth 에러 발생
onError(e.message!);
} catch (e) {
// Firebase auth 이외의 에러 발생
onError(e.toString());
}
}
void signIn({
required String email, // 이메일
required String password, // 비밀번호
required Function() onSuccess, // 로그인 성공시 호출되는 함수
required Function(String err) onError, // 에러 발생시 호출되는 함수
}) async {
// 로그인
if (email.isEmpty) {
onError('이메일을 입력해주세요.');
return;
} else if (password.isEmpty) {
onError('비밀번호를 입력해주세요.');
return;
}
// 로그인 시도
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
onSuccess(); // 성공 함수 호출
notifyListeners(); // 로그인 상태 변경 알림
} on FirebaseAuthException catch (e) {
// firebase auth 에러 발생
onError(e.message!);
} catch (e) {
// Firebase auth 이외의 에러 발생
onError(e.toString());
}
}
void signOut() async {
// 로그아웃
await FirebaseAuth.instance.signOut();
notifyListeners(); // 로그인 상태 변경 알림
}
}
이메일 주소를 아이디로
(2025년을 사는 현대인에게 이메일 계정이 하나쯤 없을리 없다.
물론 메일 유효성 체크 같은 건 따로 하지 않아 저런 대충 메일같이 생긴 가짜 메일도 인정이 된다는 점.)
+ 비밀번호에 대해서도 어느정도 기본적인 유효성 검증을 해주는 편리함.


이메일/소셜 로그인 및 기본적인 유효성 검증도 firebase를 통해 진행할 수 있었다.
(프로토타입이라, 비밀번호 마스킹처리까진 하지 않았다.)

실제로 입력한 데이터가 firebase에 저장되는 모습.
키가 있으면 데이터를 뽑아올 수 있는 맵의 형태와 유사하다.
너무 복잡하고 방대한 구조의 데이터가 아니라면,
간단한 학생/개인용 프로젝트 레벨에서는 충분히 쉽게 활용할 수 있어 보인다.
실제로 만들려는 프로덕트에 firebase를 적용하면 좀 더 쉽게 만들 수 있을 것 같아
개인 프로젝트 설계에 firebase를 도입하려고 한다.
flutter를 사용하고 싶었고 실제로 출시까지 염두에 두고 맥 pc를 구입까지 고려하고 있었으나
개인 업무 상황에 따라, 다른 언어로 조금 우회해야 할 것 같기는 하다..
'study > geultto' 카테고리의 다른 글
코드트리 2개월 체험후기(2) - 갭체크 신기능 이거 괜찮다? (0) | 2025.03.03 |
---|---|
백엔드 개발자의 프론트/모바일 개발 도전기 - Vue.js의 $nextTick 알아보기 (0) | 2025.03.01 |
코드트리 1개월 체험 후기 (2) _ 너 좀 달라졌다? (0) | 2025.02.02 |
동기 호출(Synchronous Call)과 비동기 호출(Asynchronous Call) 이해하기 (0) | 2025.01.19 |
2024 회고: 나의 발자취와 새로운 시도들 (2) | 2025.01.05 |
댓글