# Flashchat with Flutter & Firebase
# 0. Final Product
- Flutter X Firebase 스택으로 만든 채팅 어플리케이션
- Fire-auth가 제공하는 회원가입 / 로그인 / 로그아웃 기능
- firestore 가 제공하는 데이터베이스로 채팅기능을 구현했다.
- kihyeonkwon/flash-chat-flutter (github.com) (opens new window)
# 1. Main
- Firebase는 flutter와 마찬가지로 빠르게 업데이트가 되고 있고 그에 따라 configuration도 빠르게 변하고 있어 그때 그때 공식문서를 참고하는 것이 답이다.
main.dart
import 'package:flutter/material.dart';
import 'package:flash_chat/screens/welcome_screen.dart';
import 'package:flash_chat/screens/login_screen.dart';
import 'package:flash_chat/screens/registration_screen.dart';
import 'package:flash_chat/screens/chat_screen.dart';
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(FlashChat());
}
class FlashChat extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(initialRoute: WelcomeScreen.id, routes: {
WelcomeScreen.id: (context) => WelcomeScreen(),
LoginScreen.id: (context) => LoginScreen(),
RegistrationScreen.id: (context) => RegistrationScreen(),
ChatScreen.id: (context) => ChatScreen()
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namedRoute
를 쓰되pageName.id
를 사용하여 에러를 예방할 수 있다. 각 스크린에 id 값을 정의한다.context
의 의미에 대해서는 더 공부해야 한다. 대략 위젯의 어디인지를 알 수 있는 데이터라고 한다.
constants.dart
import 'package:flutter/material.dart';
const kSendButtonTextStyle = TextStyle(
color: Colors.lightBlueAccent,
fontWeight: FontWeight.bold,
fontSize: 18.0,
);
const kMessageTextFieldDecoration = InputDecoration(
contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
hintText: 'Type your message here...',
border: InputBorder.none,
);
const kMessageContainerDecoration = BoxDecoration(
border: Border(
top: BorderSide(color: Colors.lightBlueAccent, width: 2.0),
),
);
const kInputTextDecoration = InputDecoration(
hintText: 'input value',
contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(32.0)),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.lightBlueAccent, width: 1.0),
borderRadius: BorderRadius.all(Radius.circular(32.0)),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.lightBlueAccent, width: 2.0),
borderRadius: BorderRadius.all(Radius.circular(32.0)),
),
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- 각종 스타일링
components/rounded_button.dart
import 'package:flutter/material.dart';
class RoundedButton extends StatelessWidget {
final String text;
final Color color;
final Function onPressed;
RoundedButton(this.text, this.color, this.onPressed);
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: Material(
elevation: 5.0,
color: color,
borderRadius: BorderRadius.circular(30.0),
child: MaterialButton(
onPressed: onPressed,
minWidth: 200.0,
height: 42.0,
child: Text(text, style: TextStyle(color: Colors.white)),
),
),
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- 사용하게 될 버튼
# 2. Screens
screens/welcome_screen.dart
import 'package:flash_chat/screens/login_screen.dart';
import 'package:flash_chat/screens/registration_screen.dart';
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
import 'package:animated_text_kit/animated_text_kit.dart';
import 'package:flash_chat/components/rounded_button.dart';
class WelcomeScreen extends StatefulWidget {
static const String id = "/welcome";
_WelcomeScreenState createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen>
with SingleTickerProviderStateMixin {
AnimationController animationController;
Animation animation;
void initState() {
// TODO: implement initState
super.initState();
animationController =
AnimationController(duration: Duration(seconds: 1), vsync: this);
animation = ColorTween(begin: Colors.blueGrey, end: Colors.white)
.animate(animationController);
// CurvedAnimation(parent: animationController, curve: Curves.decelerate);
animationController.forward();
animationController.addListener(() {
setState(() {});
});
}
void dispose() {
// TODO: implement dispose
animationController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: animation.value,
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
children: <Widget>[
Hero(
tag: 'logo',
child: Container(
child: Image.asset('images/logo.png'),
height: 60,
),
),
DefaultTextStyle(
style: const TextStyle(
fontSize: 45.0,
fontWeight: FontWeight.w900,
color: Colors.grey),
child: AnimatedTextKit(
animatedTexts: [
TypewriterAnimatedText('Flash Chat',
speed: Duration(milliseconds: 100)),
],
onTap: () {
print("Tap Event");
},
),
),
],
),
SizedBox(
height: 48.0,
),
RoundedButton('Log In', Colors.lightBlueAccent, () {
Navigator.pushNamed(context, LoginScreen.id);
}),
RoundedButton('Register', Colors.blueAccent, () {
Navigator.pushNamed(context, RegistrationScreen.id);
})
],
),
),
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
- flutter에서는 animation을 제공해주는데 복잡한 css 없이 아름다운 어플리케이션을 만들 수 있다.
- 모든 animation에는 animationController를 설정해주어 동작을 제어할 수 있다.
- type_write는 3rd party
screens/registration_screen.dart
import 'package:flutter/material.dart';
import 'package:flash_chat/components/rounded_button.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'chat_screen.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
class RegistrationScreen extends StatefulWidget {
static const String id = "/register";
_RegistrationScreenState createState() => _RegistrationScreenState();
}
class _RegistrationScreenState extends State<RegistrationScreen> {
final _auth = FirebaseAuth.instance;
String email;
String password;
bool _saving = false;
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: ModalProgressHUD(
inAsyncCall: _saving,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Hero(
tag: 'logo',
child: Container(
height: 200.0,
child: Image.asset('images/logo.png'),
),
),
SizedBox(
height: 48.0,
),
TextField(
keyboardType: TextInputType.emailAddress,
textAlign: TextAlign.center,
onChanged: (value) {
email = value;
//Do something with the user input.
},
decoration: InputDecoration(
hintText: 'Enter your email',
contentPadding:
EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(32.0)),
),
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.blueAccent, width: 1.0),
borderRadius: BorderRadius.all(Radius.circular(32.0)),
),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.blueAccent, width: 2.0),
borderRadius: BorderRadius.all(Radius.circular(32.0)),
),
),
),
SizedBox(
height: 8.0,
),
TextField(
obscureText: true,
textAlign: TextAlign.center,
onChanged: (value) {
password = value;
//Do something with the user input.
},
decoration: InputDecoration(
hintText: 'Enter your password',
contentPadding:
EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(32.0)),
),
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.blueAccent, width: 1.0),
borderRadius: BorderRadius.all(Radius.circular(32.0)),
),
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.blueAccent, width: 2.0),
borderRadius: BorderRadius.all(Radius.circular(32.0)),
),
),
),
SizedBox(
height: 24.0,
),
RoundedButton('Register', Colors.blueAccent, () async {
setState(() {
_saving = true;
});
try {
final newUser = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
if (newUser != null) {
Navigator.pushNamed(context, ChatScreen.id);
}
} catch (e) {
print(e);
}
setState(() {
_saving = false;
});
}),
],
),
),
),
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
- FirebaseAuth를 이용해서 회원가입을 진행한다.
_auth.createUserWithEmailAndPassword(email: email, password: password)
_saving
을 true/false로 바꿔주면서ModalProgressHUD
로 로딩을 보여준다.
screens/login_screen.dart
import 'package:flash_chat/constants.dart';
import 'package:flutter/material.dart';
import 'package:flash_chat/components/rounded_button.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'chat_screen.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
class LoginScreen extends StatefulWidget {
static const String id = "/login";
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _auth = FirebaseAuth.instance;
String email;
String password;
bool _saving = false;
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: ModalProgressHUD(
inAsyncCall: _saving,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Hero(
tag: 'logo',
child: Container(
height: 200.0,
child: Image.asset('images/logo.png'),
),
),
SizedBox(
height: 48.0,
),
TextField(
keyboardType: TextInputType.emailAddress,
textAlign: TextAlign.center,
onChanged: (value) {
email = value;
},
decoration:
kInputTextDecoration.copyWith(hintText: "Enter your email"),
),
SizedBox(
height: 8.0,
),
TextField(
obscureText: true,
textAlign: TextAlign.center,
onChanged: (value) {
password = value;
},
decoration: kInputTextDecoration.copyWith(
hintText: "Enter your password"),
),
SizedBox(
height: 24.0,
),
RoundedButton('Log In', Colors.lightBlueAccent, () async {
setState(() {
_saving = true;
});
try {
final loginUser = await _auth.signInWithEmailAndPassword(
email: email, password: password);
Navigator.pushNamed(context, ChatScreen.id);
} catch (e) {
print(e);
}
setState(() {
_saving = false;
});
})
],
),
),
),
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
- registration과 동일하지만 회원가입만
signInWithEmailAndPassword
로 바꿔준다.
chat_screen.dart
import 'package:flutter/material.dart';
import 'package:flash_chat/constants.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class ChatScreen extends StatefulWidget {
static const String id = "/chat";
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _auth = FirebaseAuth.instance;
final _firestore = FirebaseFirestore.instance;
User loggedInUser;
String messageText;
final messageTextController = TextEditingController();
void initState() {
// TODO: implement initState
super.initState();
getCurrentUser();
}
void getCurrentUser() {
try {
final user = _auth.currentUser;
if (user != null) {
loggedInUser = user;
}
} catch (e) {
print(e);
}
}
// void getMessages() async {
// final databaseMessages = await _firestore.collection('messages').get();
// for (var message in databaseMessages.docs) {
// print(message.data());
// }
// }
void messagesStream() async {
await for (var snapshot in _firestore.collection('messages').snapshots()) {
for (var message in snapshot.docs) {
print(message.data().cast());
}
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
messagesStream();
// _auth.signOut();
// Navigator.pop(context);
//Implement logout functionality
}),
],
title: Text('⚡️Chat $loggedInUser.email'),
backgroundColor: Colors.lightBlueAccent,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
MessageStream(firestore: _firestore),
Container(
decoration: kMessageContainerDecoration,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller: messageTextController,
onChanged: (value) {
messageText = value;
},
decoration: kMessageTextFieldDecoration,
),
),
FlatButton(
onPressed: () {
messageTextController.clear();
try {
_firestore.collection('messages').add({
'text': messageText,
'sender': loggedInUser.email
});
} catch (e) {
print(e);
}
},
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
),
],
),
),
);
}
}
class MessageStream extends StatelessWidget {
const MessageStream({
Key key,
FirebaseFirestore firestore,
}) : _firestore = firestore,
super(key: key);
final FirebaseFirestore _firestore;
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('messages').snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final messages = snapshot.data.docs;
List<MessageBubble> messageWidgets = [];
for (var message in messages) {
final messageText = message.data()['text'];
final messageSender = message.data()['sender'];
final messageWidget =
MessageBubble(sender: messageSender, text: messageText);
messageWidgets.add(messageWidget);
}
return Expanded(
child: ListView(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
children: messageWidgets,
),
);
}
return Center(child: CircularProgressIndicator());
});
}
}
class MessageBubble extends StatelessWidget {
String sender = "someone";
String text = "something";
MessageBubble({this.sender, this.text});
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(sender, style: TextStyle(fontSize: 12, color: Colors.black)),
Material(
borderRadius: BorderRadius.circular(30),
color: Colors.lightBlueAccent,
elevation: 5,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: Text('$text',
style: TextStyle(fontSize: 15, color: Colors.white)))),
],
),
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
- 실제 채팅화면이기 때문에
auth
와firestore
를 두개다 사용하게 된다. messagesStream
을 이용해서firestore
에서 데이터가 업데이트될때마다 불러온다.ListView
위젯을 이용해서 채팅메시지를 만들어준다. 화면내부에서 스크롤 가능 영역이 생기게 된다.- 현재 기능에선 모든 사용자들이 하나의 채팅방을 사용하게 된다.