기깔나는 Todo 앱을 만들기 위해 Firestore에 DB를 만들어 저장하기로 했다. 쉽고 간편하게 만들 수 있어서 잘 모르는 사람도 뚝딱 DB를 만들 수 있다.
터미널에서 flutter pub add cloud_firestore 명령어로 플러그인 추가
// docs에는 8버전으로 되어있는데 작성 시점에서는 pod install 에러나서, 10.15.0으로 올리래서 올림
pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '10.15.0'
Streambuilder로 실시간 정보 받아오기
FirebaseFirestore.instance에서 collection의 snapshot을 받아온다. orderBy 등 조건을 걸 수도 있다.
snapshot은 Map<String, dynamic> 형식이기 때문에 내가 정의한 타입으로 형변환이 필요했다.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:intl/intl.dart';
class Todo {
String title;
String content;
String date;
String updateDate;
bool isPlanned;
bool isDoing;
bool isDone;
Todo({
required this.title,
required this.content,
required this.date,
required this.updateDate,
required this.isPlanned,
required this.isDoing,
required this.isDone,
});
// firestore로부터 조회한 데이터 형변환
factory Todo.fromFirestore(DocumentSnapshot<Map<String, dynamic>> snapshot) {
final data = snapshot.data();
return Todo(
title: data?['title'],
content: data?['content'],
date:
DateFormat.yMMMMd('ko_KR').add_jm().format(data?['date'].toDate()),
updateDate: DateFormat.yMMMMd('ko_KR')
.add_jm()
.format(data?['updateDate'].toDate()),
isPlanned: data?['isPlanned'],
isDoing: data?['isDoing'],
isDone: data?['isDone']);
}
// firestore로 전달할 데이터 형변환
Map<String, dynamic> toFirestore() {
return {
'title': title,
'content': content,
'date': date,
'updateDate': updateDate,
'isPlanned': isPlanned,
'isDoing': isDoing,
'isDone': isDone,
};
}
}
}
timestamp 형식은 intl 플러그인을 이용해서 변환했다. 사용하려면 main()에서 초기화 해주어야 한다.
firebase에서 직접 데이터를 추가하면 실시간으로 rebuild 된다.
void main() async {
// 위젯 먼저 초기화
WidgetsFlutterBinding.ensureInitialized();
// intl 초기화
await initializeDateFormatting('ko_KR', null);
// firebase 초기화
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
...
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
...
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('/todos')
.orderBy('updateDate', descending: true)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: Text('Loading...'));
}
final docs = snapshot.data!.docs;
List<Todo> newDocs = docs
.map((doc) => Todo(
title: doc['title'],
content: doc['content'],
date: DateFormat.yMMMMd('ko_KR')
.add_jm()
.format(doc['date'].toDate()),
updateDate: DateFormat.yMMMMd('ko_KR')
.add_jm()
.format(doc['updateDate'].toDate()),
isPlanned: doc['isPlanned'],
isDoing: doc['isDoing'],
isDone: doc['isDone']))
.toList();
return ListView.builder(
itemCount: newDocs.length,
itemBuilder: (BuildContext context, int index) {
Todo item = newDocs[index];
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 10),
child: Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Color.fromARGB(255, 46, 87, 55)
.withOpacity(0.5),
spreadRadius: 1,
blurRadius: 10,
offset: const Offset(0, 8),
)
],
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.title),
Text(item.content),
Text(item.updateDate),
],
),
),
),
);
},
);
}),
실행(실제 핸드폰에서 돌려서 사진이 크다)