Building chat app with Flutter and Firebase
Last updated Apr 21, 2020In Previous post we leart Firebase Authentication & Firebase database.
In this post we are going to learn how to create Chat app with Firebase Databse
Let's Create
Step 1: Create fultter application
Step 2: Create Users list page
homepage.dart
In this Page we are fetching all the users data from Firebase database and display
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_chat_demo/chat.dart';
import 'package:flutter_chat_demo/const.dart';
import 'package:flutter_chat_demo/login.dart';
import 'package:flutter_chat_demo/settings.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:google_sign_in/google_sign_in.dart';
void main() => runApp(MyApp());
class MainScreen extends StatefulWidget {
final String currentUserId;
MainScreen({Key key, @required this.currentUserId}) : super(key: key);
@override
State createState() => MainScreenState(currentUserId: currentUserId);
}
class MainScreenState extends State<MainScreen> {
MainScreenState({Key key, @required this.currentUserId});
final String currentUserId;
final FirebaseMessaging firebaseMessaging = FirebaseMessaging();
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
final GoogleSignIn googleSignIn = GoogleSignIn();
bool isLoading = false;
List<Choice> choices = const <Choice>[
const Choice(title: 'Settings', icon: Icons.settings),
const Choice(title: 'Log out', icon: Icons.exit_to_app),
];
@override
void initState() {
super.initState();
registerNotification();
configLocalNotification();
}
void registerNotification() {
firebaseMessaging.requestNotificationPermissions();
firebaseMessaging.configure(onMessage: (Map<String, dynamic> message) {
print('onMessage: $message');
Platform.isAndroid
? showNotification(message['notification'])
: showNotification(message['aps']['alert']);
return;
}, onResume: (Map<String, dynamic> message) {
print('onResume: $message');
return;
}, onLaunch: (Map<String, dynamic> message) {
print('onLaunch: $message');
return;
});
firebaseMessaging.getToken().then((token) {
print('token: $token');
Firestore.instance
.collection('users')
.document(currentUserId)
.updateData({'pushToken': token});
}).catchError((err) {
Fluttertoast.showToast(msg: err.message.toString());
});
}
void configLocalNotification() {
var initializationSettingsAndroid =
new AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = new IOSInitializationSettings();
var initializationSettings = new InitializationSettings(
initializationSettingsAndroid, initializationSettingsIOS);
flutterLocalNotificationsPlugin.initialize(initializationSettings);
}
void onItemMenuPress(Choice choice) {
if (choice.title == 'Log out') {
handleSignOut();
} else {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Settings()));
}
}
void showNotification(message) async {
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
Platform.isAndroid
? 'com.dfa.flutterchatdemo'
: 'com.duytq.flutterchatdemo',
'Flutter chat demo',
'your channel description',
playSound: true,
enableVibration: true,
importance: Importance.Max,
priority: Priority.High,
);
var iOSPlatformChannelSpecifics = new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
print(message);
// print(message['body'].toString());
// print(json.encode(message));
await flutterLocalNotificationsPlugin.show(0, message['title'].toString(),
message['body'].toString(), platformChannelSpecifics,
payload: json.encode(message));
// await flutterLocalNotificationsPlugin.show(
// 0, 'plain title', 'plain body', platformChannelSpecifics,
// payload: 'item x');
}
Future<bool> onBackPress() {
openDialog();
return Future.value(false);
}
Future<Null> openDialog() async {
switch (await showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
contentPadding:
EdgeInsets.only(left: 0.0, right: 0.0, top: 0.0, bottom: 0.0),
children: <Widget>[
Container(
color: themeColor,
margin: EdgeInsets.all(0.0),
padding: EdgeInsets.only(bottom: 10.0, top: 10.0),
height: 100.0,
child: Column(
children: <Widget>[
Container(
child: Icon(
Icons.exit_to_app,
size: 30.0,
color: Colors.white,
),
margin: EdgeInsets.only(bottom: 10.0),
),
Text(
'Exit app',
style: TextStyle(
color: Colors.white,
fontSize: 18.0,
fontWeight: FontWeight.bold),
),
Text(
'Are you sure to exit app?',
style: TextStyle(color: Colors.white70, fontSize: 14.0),
),
],
),
),
SimpleDialogOption(
onPressed: () {
Navigator.pop(context, 0);
},
child: Row(
children: <Widget>[
Container(
child: Icon(
Icons.cancel,
color: primaryColor,
),
margin: EdgeInsets.only(right: 10.0),
),
Text(
'CANCEL',
style: TextStyle(
color: primaryColor, fontWeight: FontWeight.bold),
)
],
),
),
SimpleDialogOption(
onPressed: () {
Navigator.pop(context, 1);
},
child: Row(
children: <Widget>[
Container(
child: Icon(
Icons.check_circle,
color: primaryColor,
),
margin: EdgeInsets.only(right: 10.0),
),
Text(
'YES',
style: TextStyle(
color: primaryColor, fontWeight: FontWeight.bold),
)
],
),
),
],
);
})) {
case 0:
break;
case 1:
exit(0);
break;
}
}
Future<Null> handleSignOut() async {
this.setState(() {
isLoading = true;
});
await FirebaseAuth.instance.signOut();
await googleSignIn.disconnect();
await googleSignIn.signOut();
this.setState(() {
isLoading = false;
});
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => MyApp()),
(Route<dynamic> route) => false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xffFEF5E7),
appBar: AppBar(
automaticallyImplyLeading: false,
elevation: 0.0,
title: Text(
'MAIN',
style: TextStyle(color: textWhite, fontWeight: FontWeight.bold),
),
centerTitle: true,
actions: <Widget>[
PopupMenuButton<Choice>(
onSelected: onItemMenuPress,
itemBuilder: (BuildContext context) {
return choices.map((Choice choice) {
return PopupMenuItem<Choice>(
value: choice,
child: Row(
children: <Widget>[
Icon(
choice.icon,
color: primaryColor,
),
Container(
width: 10.0,
),
Text(
choice.title,
style: TextStyle(color: primaryColor),
),
],
));
}).toList();
},
),
],
),
body: WillPopScope(
child: Stack(
children: <Widget>[
// List
Container(
child: StreamBuilder(
stream: Firestore.instance.collection('users').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
);
} else {
return ListView.builder(
padding: EdgeInsets.all(10.0),
itemBuilder: (context, index) =>
buildItem(context, snapshot.data.documents[index]),
itemCount: snapshot.data.documents.length,
);
}
},
),
),
// Loading
Positioned(
child: isLoading
? Container(
child: Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(themeColor)),
),
color: Colors.white.withOpacity(0.8),
)
: Container(),
)
],
),
onWillPop: onBackPress,
),
);
}
Widget buildItem(BuildContext context, DocumentSnapshot document) {
if (document['id'] == currentUserId) {
return Container();
} else {
return Container(
child: FlatButton(
child: Row(
children: <Widget>[
Material(
child: document['photoUrl'] != null
? CachedNetworkImage(
placeholder: (context, url) => Container(
child: CircularProgressIndicator(
strokeWidth: 1.0,
valueColor:
AlwaysStoppedAnimation<Color>(themeColor),
),
width: 50.0,
height: 50.0,
padding: EdgeInsets.all(15.0),
),
imageUrl: document['photoUrl'],
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
)
: Icon(
Icons.account_circle,
size: 50.0,
color: greyColor,
),
borderRadius: BorderRadius.all(Radius.circular(25.0)),
clipBehavior: Clip.hardEdge,
),
Flexible(
child: Container(
child: Column(
children: <Widget>[
Container(
child: Text(
'Nickname: ${document['nickname']}',
style: TextStyle(color: primaryColor),
),
alignment: Alignment.centerLeft,
margin: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 5.0),
),
Container(
child: Text(
'About me: ${document['aboutMe'] ?? 'Not available'}',
style: TextStyle(color: primaryColor),
),
alignment: Alignment.centerLeft,
margin: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 0.0),
)
],
),
margin: EdgeInsets.only(left: 20.0),
),
),
],
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Chat(
peerId: document.documentID,
peerAvatar: document['photoUrl'],
)));
},
color: greyColor2,
padding: EdgeInsets.fromLTRB(25.0, 10.0, 25.0, 10.0),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
),
margin: EdgeInsets.only(bottom: 10.0, left: 5.0, right: 5.0),
);
}
}
}
class Choice {
const Choice({this.title, this.icon});
final String title;
final IconData icon;
}
|
Step 3: Create Chat page
chatpage.dart
In this page we are sending the Messages to selected user and display list of messages between two users
import 'dart:async';
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_chat_demo/const.dart';
import 'package:flutter_chat_demo/fullPhoto.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Chat extends StatelessWidget {
final String peerId;
final String peerAvatar;
Chat({Key key, @required this.peerId, @required this.peerAvatar}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
elevation: 0.0,
title: new Text(
'CHAT',
style: TextStyle(color: textWhite, fontWeight: FontWeight.bold),
),
centerTitle: true,
),
body: new ChatScreen(
peerId: peerId,
peerAvatar: peerAvatar,
),
);
}
}
class ChatScreen extends StatefulWidget {
final String peerId;
final String peerAvatar;
ChatScreen({Key key, @required this.peerId, @required this.peerAvatar}) : super(key: key);
@override
State createState() => new ChatScreenState(peerId: peerId, peerAvatar: peerAvatar);
}
class ChatScreenState extends State<ChatScreen> {
ChatScreenState({Key key, @required this.peerId, @required this.peerAvatar});
String peerId;
String peerAvatar;
String id;
var listMessage;
String groupChatId;
SharedPreferences prefs;
File imageFile;
bool isLoading;
bool isShowSticker;
String imageUrl;
final TextEditingController textEditingController = new TextEditingController();
final ScrollController listScrollController = new ScrollController();
final FocusNode focusNode = new FocusNode();
@override
void initState() {
super.initState();
focusNode.addListener(onFocusChange);
groupChatId = '';
isLoading = false;
isShowSticker = false;
imageUrl = '';
readLocal();
}
void onFocusChange() {
if (focusNode.hasFocus) {
// Hide sticker when keyboard appear
setState(() {
isShowSticker = false;
});
}
}
readLocal() async {
prefs = await SharedPreferences.getInstance();
id = prefs.getString('id') ?? '';
if (id.hashCode <= peerId.hashCode) {
groupChatId = '$id-$peerId';
} else {
groupChatId = '$peerId-$id';
}
Firestore.instance.collection('users').document(id).updateData({'chattingWith': peerId});
setState(() {});
}
Future getImage() async {
imageFile = await ImagePicker.pickImage(source: ImageSource.gallery);
if (imageFile != null) {
setState(() {
isLoading = true;
});
uploadFile();
}
}
void getSticker() {
// Hide keyboard when sticker appear
focusNode.unfocus();
setState(() {
isShowSticker = !isShowSticker;
});
}
Future uploadFile() async {
String fileName = DateTime.now().millisecondsSinceEpoch.toString();
StorageReference reference = FirebaseStorage.instance.ref().child(fileName);
StorageUploadTask uploadTask = reference.putFile(imageFile);
StorageTaskSnapshot storageTaskSnapshot = await uploadTask.onComplete;
storageTaskSnapshot.ref.getDownloadURL().then((downloadUrl) {
imageUrl = downloadUrl;
setState(() {
isLoading = false;
onSendMessage(imageUrl, 1);
});
}, onError: (err) {
setState(() {
isLoading = false;
});
Fluttertoast.showToast(msg: 'This file is not an image');
});
}
void onSendMessage(String content, int type) {
// type: 0 = text, 1 = image, 2 = sticker
if (content.trim() != '') {
textEditingController.clear();
var documentReference = Firestore.instance
.collection('messages')
.document(groupChatId)
.collection(groupChatId)
.document(DateTime.now().millisecondsSinceEpoch.toString());
Firestore.instance.runTransaction((transaction) async {
await transaction.set(
documentReference,
{
'idFrom': id,
'idTo': peerId,
'timestamp': DateTime.now().millisecondsSinceEpoch.toString(),
'content': content,
'type': type
},
);
});
listScrollController.animateTo(0.0, duration: Duration(milliseconds: 300), curve: Curves.easeOut);
} else {
Fluttertoast.showToast(msg: 'Nothing to send');
}
}
Widget buildItem(int index, DocumentSnapshot document) {
if (document['idFrom'] == id) {
// Right (my message)
return Row(
children: <Widget>[
document['type'] == 0
// Text
? Container(
child: Text(
document['content'],
style: TextStyle(color: textWhite,fontSize: 16),
),
padding: EdgeInsets.all(15.0),
width: 200.0,
decoration: BoxDecoration(
color: chatbox,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15),
topLeft: Radius.circular(15),
topRight: Radius.circular(25),
),
),
margin: EdgeInsets.only(bottom: isLastMessageRight(index) ? 20.0 : 10.0, right: 10.0),
)
: document['type'] == 1
// Image
? Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(color: Colors.teal,width: 1),
borderRadius: BorderRadius.all(Radius.circular(8))
),
child: FlatButton(
child: Material(
child: CachedNetworkImage(
placeholder: (context, url) => Container(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
width: 200.0,
height: 200.0,
padding: EdgeInsets.all(70.0),
decoration: BoxDecoration(
color: greyColor2,
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
),
),
errorWidget: (context, url, error) => Material(
child: Image.asset(
'images/img_not_available.jpeg',
width: 200.0,
height: 200.0,
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
clipBehavior: Clip.hardEdge,
),
imageUrl: document['content'],
width: 200.0,
height: 200.0,
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(Radius.circular(8.0)),
clipBehavior: Clip.hardEdge,
),
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => FullPhoto(url: document['content'])));
},
padding: EdgeInsets.all(0),
),
margin: EdgeInsets.only(bottom: isLastMessageRight(index) ? 20.0 : 10.0, right: 10.0),
)
// Sticker
: Container(
child: new Image.asset(
'images/${document['content']}.gif',
width: 100.0,
height: 100.0,
fit: BoxFit.cover,
),
margin: EdgeInsets.only(bottom: isLastMessageRight(index) ? 20.0 : 10.0, right: 10.0),
),
],
mainAxisAlignment: MainAxisAlignment.end,
);
} else {
// Left (peer message)
return Container(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
isLastMessageLeft(index)
? Material(
child: CachedNetworkImage(
placeholder: (context, url) => Container(
child: CircularProgressIndicator(
strokeWidth: 1.0,
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
width: 35.0,
height: 35.0,
padding: EdgeInsets.all(10.0),
),
imageUrl: peerAvatar,
width: 35.0,
height: 35.0,
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(
Radius.circular(18.0),
),
clipBehavior: Clip.hardEdge,
)
: Container(width: 35.0),
document['type'] == 0
? Container(
child: Text(
document['content'],
style: TextStyle(color: Colors.white,fontSize: 16),
),
padding: EdgeInsets.all(15.0),
width: 200.0,
decoration: BoxDecoration(
color: chatbox2,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(15),
topLeft: Radius.circular(25),
topRight: Radius.circular(15),
),
),
margin: EdgeInsets.only(left: 10.0),
)
: document['type'] == 1
? Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(color: Colors.teal,width: 1),
borderRadius: BorderRadius.all(Radius.circular(8))
),
child: FlatButton(
child: Material(
child: CachedNetworkImage(
placeholder: (context, url) => Container(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
width: 200.0,
height: 200.0,
padding: EdgeInsets.all(70.0),
decoration: BoxDecoration(
color: greyColor2,
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
),
),
errorWidget: (context, url, error) => Material(
child: Image.asset(
'images/img_not_available.jpeg',
width: 200.0,
height: 200.0,
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
clipBehavior: Clip.hardEdge,
),
imageUrl: document['content'],
width: 200.0,
height: 200.0,
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(Radius.circular(8.0)),
clipBehavior: Clip.hardEdge,
),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => FullPhoto(url: document['content'])));
},
padding: EdgeInsets.all(0),
),
margin: EdgeInsets.only(left: 10.0),
)
: Container(
child: new Image.asset(
'images/${document['content']}.gif',
width: 100.0,
height: 100.0,
fit: BoxFit.cover,
),
margin: EdgeInsets.only(bottom: isLastMessageRight(index) ? 20.0 : 10.0, right: 10.0),
),
],
),
// Time
isLastMessageLeft(index)
? Container(
child: Text(
DateFormat('dd MMM kk:mm')
.format(DateTime.fromMillisecondsSinceEpoch(int.parse(document['timestamp']))),
style: TextStyle(color: chatbox, fontSize: 12.0, fontStyle: FontStyle.italic),
),
margin: EdgeInsets.only(left: 50.0, top: 5.0, bottom: 5.0),
)
: Container()
],
crossAxisAlignment: CrossAxisAlignment.start,
),
margin: EdgeInsets.only(bottom: 10.0),
);
}
}
bool isLastMessageLeft(int index) {
if ((index > 0 && listMessage != null && listMessage[index - 1]['idFrom'] == id) || index == 0) {
return true;
} else {
return false;
}
}
bool isLastMessageRight(int index) {
if ((index > 0 && listMessage != null && listMessage[index - 1]['idFrom'] != id) || index == 0) {
return true;
} else {
return false;
}
}
Future<bool> onBackPress() {
if (isShowSticker) {
setState(() {
isShowSticker = false;
});
} else {
Firestore.instance.collection('users').document(id).updateData({'chattingWith': null});
Navigator.pop(context);
}
return Future.value(false);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
child: Container(
color: Color(0xffFEF5E7),
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
// List of messages
buildListMessage(),
// Sticker
(isShowSticker ? buildSticker() : Container()),
// Input content
buildInput(),
],
),
// Loading
buildLoading()
],
),
),
onWillPop: onBackPress,
);
}
Widget buildSticker() {
return Container(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
FlatButton(
onPressed: () => onSendMessage('mimi1', 2),
child: new Image.asset(
'images/mimi1.gif',
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
),
),
FlatButton(
onPressed: () => onSendMessage('mimi2', 2),
child: new Image.asset(
'images/mimi2.gif',
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
),
),
FlatButton(
onPressed: () => onSendMessage('mimi3', 2),
child: new Image.asset(
'images/mimi3.gif',
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
),
)
],
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
Row(
children: <Widget>[
FlatButton(
onPressed: () => onSendMessage('mimi4', 2),
child: new Image.asset(
'images/mimi4.gif',
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
),
),
FlatButton(
onPressed: () => onSendMessage('mimi5', 2),
child: new Image.asset(
'images/mimi5.gif',
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
),
),
FlatButton(
onPressed: () => onSendMessage('mimi6', 2),
child: new Image.asset(
'images/mimi6.gif',
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
),
)
],
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
Row(
children: <Widget>[
FlatButton(
onPressed: () => onSendMessage('mimi7', 2),
child: new Image.asset(
'images/mimi7.gif',
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
),
),
FlatButton(
onPressed: () => onSendMessage('mimi8', 2),
child: new Image.asset(
'images/mimi8.gif',
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
),
),
FlatButton(
onPressed: () => onSendMessage('mimi9', 2),
child: new Image.asset(
'images/mimi9.gif',
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
),
)
],
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
)
],
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
decoration: new BoxDecoration(
border: new Border(top: new BorderSide(color: greyColor2, width: 0.5)), color: Colors.white),
padding: EdgeInsets.all(5.0),
height: 180.0,
);
}
Widget buildLoading() {
return Positioned(
child: isLoading
? Container(
child: Center(
child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(themeColor)),
),
color: Colors.white.withOpacity(0.8),
)
: Container(),
);
}
Widget buildInput() {
return Container(
child: Row(
children: <Widget>[
// Button send image
Material(
child: new Container(
margin: new EdgeInsets.symmetric(horizontal: 1.0),
child: new IconButton(
icon: new Icon(Icons.image),
onPressed: getImage,
color: pink,
),
),
color: Colors.white,
),
Material(
child: new Container(
margin: new EdgeInsets.symmetric(horizontal: 1.0),
child: new IconButton(
icon: new Icon(Icons.face),
onPressed: getSticker,
color: pink,
),
),
color: Colors.white,
),
// Edit text
Flexible(
child: Container(
child: TextField(
style: TextStyle(color: primaryColor, fontSize: 15.0),
controller: textEditingController,
decoration: InputDecoration.collapsed(
hintText: 'Type your message...',
hintStyle: TextStyle(color: pink),
),
focusNode: focusNode,
),
),
),
// Button send message
Material(
child: new Container(
margin: new EdgeInsets.symmetric(horizontal: 8.0),
child: new IconButton(
icon: new Icon(Icons.send),
onPressed: () => onSendMessage(textEditingController.text, 0),
color: pink,
),
),
color: Colors.white,
),
],
),
width: double.infinity,
height: 50.0,
decoration: new BoxDecoration(
border: new Border(top: new BorderSide(color: greyColor2, width: 0.5)), color: Colors.white),
);
}
Widget buildListMessage() {
return Flexible(
child: groupChatId == ''
? Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(themeColor)))
: StreamBuilder(
stream: Firestore.instance
.collection('messages')
.document(groupChatId)
.collection(groupChatId)
.orderBy('timestamp', descending: true)
.limit(20)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(themeColor)));
} else {
listMessage = snapshot.data.documents;
return ListView.builder(
padding: EdgeInsets.all(10.0),
itemBuilder: (context, index) => buildItem(index, snapshot.data.documents[index]),
itemCount: snapshot.data.documents.length,
reverse: true,
controller: listScrollController,
);
}
},
),
);
}
}
|
Step 4: Settings Page
import 'dart:async';
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_chat_demo/const.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_picker/image_picker.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Settings extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(
'SETTINGS',
style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
),
centerTitle: true,
),
body: new SettingsScreen(),
);
}
}
class SettingsScreen extends StatefulWidget {
@override
State createState() => new SettingsScreenState();
}
class SettingsScreenState extends State<SettingsScreen> {
TextEditingController controllerNickname;
TextEditingController controllerAboutMe;
SharedPreferences prefs;
String id = '';
String nickname = '';
String aboutMe = '';
String photoUrl = '';
bool isLoading = false;
File avatarImageFile;
final FocusNode focusNodeNickname = new FocusNode();
final FocusNode focusNodeAboutMe = new FocusNode();
@override
void initState() {
super.initState();
readLocal();
}
void readLocal() async {
prefs = await SharedPreferences.getInstance();
id = prefs.getString('id') ?? '';
nickname = prefs.getString('nickname') ?? '';
aboutMe = prefs.getString('aboutMe') ?? '';
photoUrl = prefs.getString('photoUrl') ?? '';
controllerNickname = new TextEditingController(text: nickname);
controllerAboutMe = new TextEditingController(text: aboutMe);
// Force refresh input
setState(() {});
}
Future getImage() async {
File image = await ImagePicker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
avatarImageFile = image;
isLoading = true;
});
}
uploadFile();
}
Future uploadFile() async {
String fileName = id;
StorageReference reference = FirebaseStorage.instance.ref().child(fileName);
StorageUploadTask uploadTask = reference.putFile(avatarImageFile);
StorageTaskSnapshot storageTaskSnapshot;
uploadTask.onComplete.then((value) {
if (value.error == null) {
storageTaskSnapshot = value;
storageTaskSnapshot.ref.getDownloadURL().then((downloadUrl) {
photoUrl = downloadUrl;
Firestore.instance
.collection('users')
.document(id)
.updateData({'nickname': nickname, 'aboutMe': aboutMe, 'photoUrl': photoUrl}).then((data) async {
await prefs.setString('photoUrl', photoUrl);
setState(() {
isLoading = false;
});
Fluttertoast.showToast(msg: "Upload success");
}).catchError((err) {
setState(() {
isLoading = false;
});
Fluttertoast.showToast(msg: err.toString());
});
}, onError: (err) {
setState(() {
isLoading = false;
});
Fluttertoast.showToast(msg: 'This file is not an image');
});
} else {
setState(() {
isLoading = false;
});
Fluttertoast.showToast(msg: 'This file is not an image');
}
}, onError: (err) {
setState(() {
isLoading = false;
});
Fluttertoast.showToast(msg: err.toString());
});
}
void handleUpdateData() {
focusNodeNickname.unfocus();
focusNodeAboutMe.unfocus();
setState(() {
isLoading = true;
});
Firestore.instance
.collection('users')
.document(id)
.updateData({'nickname': nickname, 'aboutMe': aboutMe, 'photoUrl': photoUrl}).then((data) async {
await prefs.setString('nickname', nickname);
await prefs.setString('aboutMe', aboutMe);
await prefs.setString('photoUrl', photoUrl);
setState(() {
isLoading = false;
});
Fluttertoast.showToast(msg: "Update success");
}).catchError((err) {
setState(() {
isLoading = false;
});
Fluttertoast.showToast(msg: err.toString());
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
SingleChildScrollView(
child: Column(
children: <Widget>[
// Avatar
Container(
child: Center(
child: Stack(
children: <Widget>[
(avatarImageFile == null)
? (photoUrl != ''
? Material(
child: CachedNetworkImage(
placeholder: (context, url) => Container(
child: CircularProgressIndicator(
strokeWidth: 2.0,
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
width: 90.0,
height: 90.0,
padding: EdgeInsets.all(20.0),
),
imageUrl: photoUrl,
width: 90.0,
height: 90.0,
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(Radius.circular(45.0)),
clipBehavior: Clip.hardEdge,
)
: Icon(
Icons.account_circle,
size: 90.0,
color: greyColor,
))
: Material(
child: Image.file(
avatarImageFile,
width: 90.0,
height: 90.0,
fit: BoxFit.cover,
),
borderRadius: BorderRadius.all(Radius.circular(45.0)),
clipBehavior: Clip.hardEdge,
),
IconButton(
icon: Icon(
Icons.camera_alt,
color: primaryColor.withOpacity(0.5),
),
onPressed: getImage,
padding: EdgeInsets.all(30.0),
splashColor: Colors.transparent,
highlightColor: greyColor,
iconSize: 30.0,
),
],
),
),
width: double.infinity,
margin: EdgeInsets.all(20.0),
),
// Input
Column(
children: <Widget>[
// Username
Container(
child: Text(
'Nickname',
style: TextStyle(fontStyle: FontStyle.italic, fontWeight: FontWeight.bold, color: primaryColor),
),
margin: EdgeInsets.only(left: 10.0, bottom: 5.0, top: 10.0),
),
Container(
child: Theme(
data: Theme.of(context).copyWith(primaryColor: primaryColor),
child: TextField(
decoration: InputDecoration(
hintText: 'Sweetie',
contentPadding: new EdgeInsets.all(5.0),
hintStyle: TextStyle(color: greyColor),
),
controller: controllerNickname,
onChanged: (value) {
nickname = value;
},
focusNode: focusNodeNickname,
),
),
margin: EdgeInsets.only(left: 30.0, right: 30.0),
),
// About me
Container(
child: Text(
'About me',
style: TextStyle(fontStyle: FontStyle.italic, fontWeight: FontWeight.bold, color: primaryColor),
),
margin: EdgeInsets.only(left: 10.0, top: 30.0, bottom: 5.0),
),
Container(
child: Theme(
data: Theme.of(context).copyWith(primaryColor: primaryColor),
child: TextField(
decoration: InputDecoration(
hintText: 'Fun, like travel and play PES...',
contentPadding: EdgeInsets.all(5.0),
hintStyle: TextStyle(color: greyColor),
),
controller: controllerAboutMe,
onChanged: (value) {
aboutMe = value;
},
focusNode: focusNodeAboutMe,
),
),
margin: EdgeInsets.only(left: 30.0, right: 30.0),
),
],
crossAxisAlignment: CrossAxisAlignment.start,
),
// Button
Container(
child: FlatButton(
onPressed: handleUpdateData,
child: Text(
'UPDATE',
style: TextStyle(fontSize: 16.0),
),
color: primaryColor,
highlightColor: new Color(0xff8d93a0),
splashColor: Colors.transparent,
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(30.0, 10.0, 30.0, 10.0),
),
margin: EdgeInsets.only(top: 50.0, bottom: 50.0),
),
],
),
padding: EdgeInsets.only(left: 15.0, right: 15.0),
),
// Loading
Positioned(
child: isLoading
? Container(
child: Center(
child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(themeColor)),
),
color: Colors.white.withOpacity(0.8),
)
: Container(),
),
],
);
}
}
|
Step 5: Photo Page
import 'package:flutter/material.dart';
import 'package:flutter_chat_demo/const.dart';
import 'package:photo_view/photo_view.dart';
class FullPhoto extends StatelessWidget {
final String url;
FullPhoto({Key key, @required this.url}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(
'FULL PHOTO',
style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
),
centerTitle: true,
),
body: new FullPhotoScreen(url: url),
);
}
}
class FullPhotoScreen extends StatefulWidget {
final String url;
FullPhotoScreen({Key key, @required this.url}) : super(key: key);
@override
State createState() => new FullPhotoScreenState(url: url);
}
class FullPhotoScreenState extends State<FullPhotoScreen> {
final String url;
FullPhotoScreenState({Key key, @required this.url});
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(child: PhotoView(imageProvider: NetworkImage(url)));
}
}
|
Step 6: Constants page
import 'dart:ui';
import 'package:flutter/material.dart';
//final themeColor = Color(0xff15EC70);
final themeColor = Colors.teal;
final primaryColor = Color(0xff203152);
final greyColor = Color(0xffaeaeae);
final greyColor2 = Color(0xffE8E8E8);
final chatbox = Color(0xff7D3C98);
final chatbox2 = Color(0xff7F8C8D);
final textWhite = Color(0xffFAFAFA);
final pink = Colors.pink;
|
Step 7: Run application
Find Login page at Previous post
Article Contributed By :
|
|
|
|
3408 Views |