์ด๋ฒ ํฌ์คํ ์์๋
์ ๋ฒ ํฌ์คํ ์ ์ด์ด ๋ฉ์์ง ์ ์ฅํ๊ธฐ ๊ธฐ๋ฅ์ ๊ตฌํํด๋ณผ ์์ ์ด๋ค.
์ฐ์ ๋ชจ๋ธ ํด๋์ค๋ฅผ ์ฝ๊ฐ ๋ ์์ ํ์๋ค.
message_model.dart
import 'package:cloud_firestore/cloud_firestore.dart';
class MessageModel {
final String id; //ํด๋น ๋ํ๋จผํธ์ ID๋ฅผ ๋ด๊ธฐ์ํจ.
final String content;
final Timestamp sendDate;
MessageModel({
this.id = '',
this.content = '',
Timestamp? sendDate,
}):sendDate = sendDate??Timestamp(0, 0);
//์๋ฒ๋ก๋ถํฐ mapํํ์ ์๋ฃ๋ฅผ MessageModelํํ์ ์๋ฃ๋ก ๋ณํํด์ฃผ๋ ์ญํ ์ ์ํํจ.
factory MessageModel.fromMap({required String id,required Map<String,dynamic> map}){
return MessageModel(
id: id,
content: map['content']??'',
sendDate: map['sendDate']??Timestamp(0, 0)
);
}
Map<String,dynamic> toMap(){
Map<String,dynamic> data = {};
data['content']=content;
data['sendDate']=sendDate;
return data;
}
}
์ด๋ฒ์ timestamp๋ผ๋ ํญ๋ชฉ์ด ์ถ๊ฐ๋์๋๋ฐ
์กฐํ๋ ๋ฉ์์ง์ ๋ํ ์ ์ก ์ผ์๋ฅผ ๊ฐ์ง๊ณ ์๋ ์ํ์ด๋ค.
๋ค์์ ๋ชฉ๋ก ํด๋์ค์ ๋ํ ์์ ์ ์งํํ๋ค.
message_list_screen.dart ( onPressedSendButton ๋ฉ์๋ ์ถ๊ฐ)
void _onPressedSendButton(){
try{
//์๋ฒ๋ก ๋ณด๋ผ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ธํด๋์ค์ ๋ด์๋๋ค.
//์ฌ๊ธฐ์ sendDate์ Timestamp.now()๊ฐ ๋ค์ด๊ฐ๋๋ฐ ์ด๋ ๋๋ฐ์ด์ค์ ์๊ฐ์ ๋ํ๋ด๋ฏ๋ก ๋์ค์๋ ์๋ฒ์ ์๊ฐ์ ๋ฃ๋ ๋ฐฉ๋ฒ์ผ๋ก ๋ณ๊ฒฝํ๋๋ก ํ์.
MessageModel messageModel = MessageModel(content: controller.text,sendDate: Timestamp.now());
//Firestore ์ธ์คํด์ค ๊ฐ์ ธ์ค๊ธฐ
FirebaseFirestore firestore = FirebaseFirestore.instance;
//์ํ๋ collection ์ฃผ์์ ์๋ก์ด document๋ฅผ Map์ ํํ๋ก ์ถ๊ฐํ๋ ๋ชจ์ต.
firestore.collection('chatrooms/YLCoRBj59XRsDdav2YV1/messages').add(messageModel.toMap());
}catch(ex){
log('error)',error: ex.toString(),stackTrace: StackTrace.current);
}
}
ํด๋น ์ฝ๋๋ "์ ์ก" ๋ฒํผ์ ๋๋ ์ ๋ ๋์ํ๋ ์ฝ๋์ด๋ค.
ํ์ด์ด์คํ ์ด์ db๋ RDBMS์๋ ๋ค๋ฅด๊ฒ ๋ฐ์ดํฐ๋ค์ด ์ ํํ๋ ์ํ๋ก ์ ๋ ฅ๋์ผ๋๋ ๊ฒ ์๋๊ธฐ ๋๋ฌธ์
์ธ์ ๋ ์ง ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ๋ ์๋ ํ๋๋ฅผ ์ถ๊ฐํด์ ๋ฃ๊ฑฐ๋ ์๋ ์ปฌ๋ ์ ์ด ์ถ๊ฐ๋ ์ํ๋ก ๋ฃ๊ฑฐ๋ ํ๋ ๊ฒ ๊ฐ๋ฅํ๋ค.
message_list_screen.dart ( getInputWidget ๋ฉ์๋ ์ถ๊ฐ)
Widget getInputWidget() {
return Container(
height: 60,
width: double.infinity,
decoration: BoxDecoration(boxShadow: const [
BoxShadow(color: Colors.black12, offset: Offset(0, -2), blurRadius: 3)
], color: Theme.of(context).bottomAppBarColor),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 15,vertical: 8),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: TextField(
controller: controller,
decoration: InputDecoration(
labelStyle: TextStyle(fontSize: 15),
labelText: "๋ด์ฉ์ ์
๋ ฅํ์ธ์..",
fillColor: Colors.white,
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Colors.blue,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Colors.black26,
width: 1.0,
),
),
),
),
),
SizedBox(width: 10,),
RawMaterialButton(
onPressed: _onPressedSendButton, //์ ์ก๋ฒํผ์ ๋๋ฅผ๋ ๋์์ํฌ ๋ฉ์๋
constraints: BoxConstraints(
minWidth: 0,
minHeight: 0
),
elevation: 2,
fillColor: Theme.of(context).colorScheme.primary,
shape: CircleBorder(),
child: Padding(
padding: EdgeInsets.all(10),
child: Icon(Icons.send),
),
)
],
),
),
);
}
ํด๋น ์ฝ๋๋ ํ๋จ์ ๋ฃ์ ์ ๋ ฅ ์นธ์ ๋ํ UI ๊ตฌ์ฑ์ด๋ค
์ด์ MessageListScreenํด๋์ค์ ๋ํ ์ ์ฒด ์ฝ๋๋ฅผ ์ดํด๋ณด๋๋ก ํ์.
message_list_screen.dart (์ ์ฒด ์ฝ๋)
import 'dart:developer';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test_chatapp/model/message_model.dart';
class MessageListScreen extends StatefulWidget {
const MessageListScreen({Key? key}) : super(key: key);
@override
State<MessageListScreen> createState() => _MessageListScreenState();
}
class _MessageListScreenState extends State<MessageListScreen> {
TextEditingController controller = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('๋ฉ์ธ์ง ๋ชฉ๋ก')),
body: StreamBuilder<List<MessageModel>>(
stream: streamMessages(), //์ค๊ณํ๊ณ ์ถ์ Stream์ ๋ฃ๋๋ค.
builder: (context, asyncSnapshot) {
if (!asyncSnapshot.hasData) {
//๋ฐ์ดํฐ๊ฐ ์์ ๊ฒฝ์ฐ ๋ก๋ฉ์์ ฏ์ ํ์ํ๋ค.
return const Center(child: CircularProgressIndicator());
} else if (asyncSnapshot.hasError) {
return const Center(
child: Text('์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.'),
);
} else {
List<MessageModel> messages = asyncSnapshot.data!; //๋น๋๊ธฐ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ ๊ฒฝ์ฐ ๋ฆฌ์คํธ๋ทฐ ํ์
return Column(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: ListView.builder(
// ์์ ์์ ฏ์ ํฌ๊ธฐ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ก๋๊ฒ ์๋ ์์์์ ฏ์ ํฌ๊ธฐ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ก์
itemCount: messages.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(messages[index].content),
subtitle: Text(messages[index].sendDate.toDate().toLocal().toString().substring(5,16)),
);
})),
getInputWidget()
],
);
}
},
),
);
}
Widget getInputWidget() {
return Container(
height: 60,
width: double.infinity,
decoration: BoxDecoration(boxShadow: const [
BoxShadow(color: Colors.black12, offset: Offset(0, -2), blurRadius: 3)
], color: Theme.of(context).bottomAppBarColor),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 15,vertical: 8),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: TextField(
controller: controller,
decoration: InputDecoration(
labelStyle: TextStyle(fontSize: 15),
labelText: "๋ด์ฉ์ ์
๋ ฅํ์ธ์..",
fillColor: Colors.white,
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Colors.blue,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Colors.black26,
width: 1.0,
),
),
),
),
),
SizedBox(width: 10,),
RawMaterialButton(
onPressed: _onPressedSendButton, //์ ์ก๋ฒํผ์ ๋๋ฅผ๋ ๋์์ํฌ ๋ฉ์๋
constraints: BoxConstraints(
minWidth: 0,
minHeight: 0
),
elevation: 2,
fillColor: Theme.of(context).colorScheme.primary,
shape: CircleBorder(),
child: Padding(
padding: EdgeInsets.all(10),
child: Icon(Icons.send),
),
)
],
),
),
);
}
void _onPressedSendButton(){
try{
//์๋ฒ๋ก ๋ณด๋ผ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ธํด๋์ค์ ๋ด์๋๋ค.
//์ฌ๊ธฐ์ sendDate์ Timestamp.now()๊ฐ ๋ค์ด๊ฐ๋๋ฐ ์ด๋ ๋๋ฐ์ด์ค์ ์๊ฐ์ ๋ํ๋ด๋ฏ๋ก ๋์ค์๋ ์๋ฒ์ ์๊ฐ์ ๋ฃ๋ ๋ฐฉ๋ฒ์ผ๋ก ๋ณ๊ฒฝํ๋๋ก ํ์.
MessageModel messageModel = MessageModel(content: controller.text,sendDate: Timestamp.now());
//Firestore ์ธ์คํด์ค ๊ฐ์ ธ์ค๊ธฐ
FirebaseFirestore firestore = FirebaseFirestore.instance;
//์ํ๋ collection ์ฃผ์์ ์๋ก์ด document๋ฅผ Map์ ํํ๋ก ์ถ๊ฐํ๋ ๋ชจ์ต.
firestore.collection('chatrooms/YLCoRBj59XRsDdav2YV1/messages').add(messageModel.toMap());
}catch(ex){
log('error)',error: ex.toString(),stackTrace: StackTrace.current);
}
}
Stream<List<MessageModel>> streamMessages(){
try{
//์ฐพ๊ณ ์ ํ๋ ์ปฌ๋ ์
์ ์ค๋
์ท(Stream)์ ๊ฐ์ ธ์จ๋ค.
final Stream<QuerySnapshot> snapshots = FirebaseFirestore.instance.collection('chatrooms/YLCoRBj59XRsDdav2YV1/messages').orderBy('sendDate').snapshots();
//์๋ญ ์ค๋
์ท(Stream)๋ด๋ถ์ ์๋ฃ๋ค์ List<MessageModel> ๋ก ๋ณํํ๊ธฐ ์ํด map์ ์ฌ์ฉํ๋๋ก ํ๋ค.
//์ฐธ๊ณ ๋ก List.map()๋ List ์์ element๋ค์ ์ํ๋ ํํ๋ก ๋ณํํ์ฌ ์๋ก์ด List๋ก ๋ฐํํ๋ค
return snapshots.map((querySnapshot){
List<MessageModel> messages = [];//querySnapshot์ message๋ก ์ฎ๊ธฐ๊ธฐ ์ํด List<MessageModel> ์ ์ธ
querySnapshot.docs.forEach((element) { //ํด๋น ์ปฌ๋ ์
์ ์กด์ฌํ๋ ๋ชจ๋ docs๋ฅผ ์ํํ๋ฉฐ messages ์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ๋ค.
messages.add(
MessageModel.fromMap(
id:element.id,
map:element.data() as Map<String, dynamic>
)
);
});
return messages; //QuerySnapshot์์ List<MessageModel> ๋ก ๋ณ๊ฒฝ์ด ๋์ผ๋ ๋ฐํ
}); //Stream<QuerySnapshot> ์์ Stream<List<MessageModel>>๋ก ๋ณ๊ฒฝ๋์ด ๋ฐํ๋จ
}catch(ex){//์ค๋ฅ ๋ฐ์ ์ฒ๋ฆฌ
log('error)',error: ex.toString(),stackTrace: StackTrace.current);
return Stream.error(ex.toString());
}
}
}
๋ง์ฝ ๋ฉ์์ง ์ ์ก ํ ๋ชฉ๋ก์ด ๋ค์ฃฝ๋ฐ์ฃฝ ์์ฌ์ ๋์จ๋ค๋ฉด
๋ฉ์์ง๋ฅผ ๊ฐ์ ธ์ค๋ ๋ถ๋ถ์์ ์ ๋ ฌ ๊ธฐ๋ฅ์ ์ถ๊ฐํด์ฃผ์ด์ผ ํ๋ค.
๋์ ๊ฒฝ์ฐ๋ ์๋์ ์ฝ๋์ฒ๋ผ
final Stream<QuerySnapshot> snapshots = FirebaseFirestore.instance.collection('chatrooms/YLCoRBj59XRsDdav2YV1/messages').orderBy('sendDate').snapshots();
order by ๋ฉ์๋๋ฅผ ์ถ๊ฐํ์ฌ sendDate๊ธฐ์ค ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๋ ฌํ๊ฒ๋ ํ๋ค.
์น์์๋ ์ ๋์ํ๋ ๋ชจ์ต์ด๋ค XD
์ฌ๊ธฐ๊น์ง ์งํํด์จ ์ ์ฒด ์์ค์ฝ๋๋ฅผ ์ฌ๋ ค๋๋๋ก ํ๊ฒ ๋ค.
ํฌ์คํ ๋ ๋ด์ฉ๊ณผ ์ฝ๊ฐ์ ์ฐจ์ด๊ฐ ์์ ์ ์์ผ๋
์ฐธ๊ณ ์ฉ์ผ๋ก๋ง ํ์ธํ๋๋ก ํ๊ณ ์ฝ๋ฉ์ ๋ณต๋ถ์ด ์๋๋ผ ์ง์ ์์ผ๋ก ์ ๋ ฅํด๋ณด๋ฉฐ ์ต๋ํ ์ดํด๋ฅผ ํด๊ฐ๋ฉฐ
๊ณต๋ถํด๊ฐ๊ธธ ๋ฐ๋๋ค.
https://github.com/jwk9022648/flutter_test_chatapp