i am creating a simple chat app using Cloud Firebase, but sometime when i am adding text it is getting added in random position, although I have used reversed to reverse the list, can anyone have some idea to solve it...
hers's my code...
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flash_chat/constants.dart';
import 'package:flutter/material.dart';
final _firestore = FirebaseFirestore.instance;
User loggedinUser;
class ChatScreen extends StatefulWidget {
static const String route = '/chat';
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _auth = FirebaseAuth.instance;
final messageTextController =
TextEditingController(); // to remove the sent message from the textbox
String messageText;
void getCurrentUser() async {
/*
* Default is null; as user logs in it contains some value
* */
try {
final user = await _auth.currentUser;
if (user != null) {
loggedinUser = user;
loggedinUser.email;
//print('res************************************: $recentLoggedInUser');
}
} catch (e) {}
}
// void getMessages() async {
// final messages = await _firestore.collection('messages').get();
// for (var message in messages.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());
// }
// }
// }
#override
void initState() {
// TODO: implement initState
super.initState();
getCurrentUser();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
// messagesStream();
_auth.signOut();
Navigator.pop(context);
}),
],
title: Text('⚡️Chat'),
backgroundColor: Colors.lightBlueAccent,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
messageStreams(), // Just get the stream here to populate
Container(
decoration: kMessageContainerDecoration,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller:
messageTextController, // this controller handels the text after being sent
onChanged: (value) {
messageText = value;
},
decoration: kMessageTextFieldDecoration,
),
),
FlatButton(
onPressed: () {
/*
* add method receives a map that accepts <String, dynamic> format;
* field name should be same as defined into firebase portal
* */
// use the controller to clear the text box as soon as it is sent
messageTextController.clear();
_firestore.collection('messages').add({
'text': messageText,
'sender': loggedinUser.email,
});
},
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
),
],
),
),
);
}
}
class messageStreams extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('messages').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.lightBlueAccent,
),
);
}
final messages = snapshot.data.docs.reversed;
// .reversed; // reversed make a list in reversed order, so newest will appear at lowest
List<messageBubble> messagewidgets = [];
for (var m in messages) {
final messageText = m.get('text');
final senderText = m.get('sender'); // get sender email from DB
var recentLogger = loggedinUser.email;
CrossAxisAlignment c = recentLogger == senderText
? CrossAxisAlignment.end
: CrossAxisAlignment.start;
final messageWidgit = messageBubble(
sender: senderText,
text: messageText,
crossAxisAlignments: c,
);
messagewidgets.add(messageWidgit);
}
/*
* As we have Container, we will use expanded, this way it will take only the part that is necessary
* */
return Expanded(
child: ListView(
reverse: true,
padding: EdgeInsets.symmetric(
horizontal: 10.0,
vertical: 20.0,
),
children: messagewidgets,
),
);
},
);
}
}
class messageBubble extends StatelessWidget {
final sender;
final text;
CrossAxisAlignment crossAxisAlignments;
messageBubble({this.sender, this.text, this.crossAxisAlignments});
#override
Widget build(BuildContext context) {
BorderRadius br;
Color clr;
if (crossAxisAlignments == CrossAxisAlignment.start) {
br = BorderRadius.only(
topRight: Radius.circular(30.0),
bottomLeft: Radius.circular(30.0),
bottomRight: Radius.circular(30.0),
);
clr = Colors.lightBlueAccent;
} else if (crossAxisAlignments == CrossAxisAlignment.end) {
br = BorderRadius.only(
topLeft: Radius.circular(30.0),
bottomLeft: Radius.circular(30.0),
bottomRight: Radius.circular(30.0),
);
clr = Colors.blueAccent;
}
return Padding(
padding: EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: crossAxisAlignments,
children: <Widget>[
Text(
sender,
style: TextStyle(
fontSize: 12.0,
color: Colors.black54,
),
),
BuildChatList(br, clr),
],
),
);
}
Material BuildChatList(BorderRadius br, Color clr) {
return Material(
borderRadius: br,
elevation: 5.0,
color: clr,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
child: Text(
'$text',
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
),
),
),
);
}
}
//BorderRadius.only(
//topLeft: Radius.circular(30.0),
//bottomLeft: Radius.circular(30.0),
//bottomRight: Radius.circular(30.0),
//)
Please follow the image:
here i have got what's up on the top, rather I did it after 2 messages.
Can anyone help me to solve the issue, thanks in advance.
You can add orderBy() to descend sorting the message. for example firestore.collection('messages').orderBy("created_at", descending: true).snapshots().
Related
I am working on a large-scale project and integrating video calls into it. So I made a separate project just for practice and I achieved good results. The group calling worked perfectly on both android and IOS. But then I integrated the same code in my large-scale project which uses firebase as a backend and when I navigate to the video screen it gives me an error saying "Unhandled Exception: LateInitializationError: Field 'requestPort' has not been initialized". The Agora console is on Testing for now and the channel wasn't expired just in case you guys are wondering. As I said it works perfectly in a separate project.
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
class VideoCallPage extends StatefulWidget {
const VideoCallPage({Key? key}) : super(key: key);
#override
VideoCallPageState createState() => VideoCallPageState();
}
class VideoCallPageState extends State<VideoCallPage> {
static final _users = <int>[];
Logger logger = Logger();
bool muted = false;
late RtcEngine _engine;
bool isRigning = true;
bool isSpeakerOn = true;
final String channelName = 'video';
final String appID = 'xxx';
final String tokenAudio ='xxx';
#override
void dispose() {
_dispose();
super.dispose();
}
Future<void> _dispose() async {
_users.clear();
await _engine.leaveChannel();
await _engine.stopPreview();
await _engine.release();
}
#override
void initState() {
super.initState();
// initialize agora sdk
initialize();
}
Future<void> initialize() async {
logger.i('Initialize');
if (appID.isEmpty) {
setState(() {
logger.e(
'APP_ID missing, please provide your APP_ID in settings.dart',
);
logger.e('Agora Engine is not starting');
});
return;
}
await _initAgoraRtcEngine();
_addAgoraEventHandlers();
onOffSpeaker();
await _engine.joinChannel(
token: tokenAudio,
channelId: channelName,
uid: 0,
options: const ChannelMediaOptions(
channelProfile: ChannelProfileType.channelProfileCommunication,
clientRoleType: ClientRoleType.clientRoleBroadcaster));
}
Future<void> _initAgoraRtcEngine() async {
logger.i('_initAgoraRtcEngine');
//create the engine
_engine = createAgoraRtcEngine();
logger.i('RtcEngineContext');
await _engine.initialize(RtcEngineContext(
appId: appID,
));
logger.i('enablbing video');
await _engine.enableVideo();
// await _engine.setVideoEncoderConfiguration(
// const VideoEncoderConfiguration(
// dimensions: VideoDimensions(width: 640, height: 360),
// frameRate: 15,
// bitrate: 0,
// ),
// );
await _engine.startPreview();
}
void _addAgoraEventHandlers() {
_engine.registerEventHandler(RtcEngineEventHandler(
onError: (ErrorCodeType errorCodeType, String value) {
if (mounted) {
setState(() {
final info = 'onError: ${errorCodeType.name}';
logger.e(info);
});
}
},
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
setState(() {
final info =
'onJoinChannel: ${connection.channelId}, uid: ${connection.localUid}';
logger.i(info);
});
},
onLeaveChannel: (RtcConnection rtcConnection, RtcStats rtcStats) {
setState(() {
logger.i('onLeaveChannel');
_users.clear();
});
},
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
setState(() {
isRigning = false;
final info = 'remoteUserJoined: $remoteUid';
logger.i(info);
_users.add(remoteUid);
});
},
onUserOffline: (RtcConnection connection, int remoteUid,
UserOfflineReasonType reason) {
setState(() {
final info =
'remoteUserOffline: $remoteUid , reason: ${reason.index}';
logger.i(info);
_users.remove(remoteUid);
});
},
onFirstRemoteVideoFrame: (connection, uid, width, height, elapsed) {
setState(() {
final info = 'firstRemoteVideoFrame: $uid';
logger.i(info);
});
},
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Agora Group Video Calling'),
),
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: <Widget>[
Opacity(
opacity: isRigning ? 0.2 : 1,
child: _viewRows(),
),
_toolbar(),
if (isRigning)
Positioned(
top: 100,
left: MediaQuery.of(context).size.width * 0.3,
right: MediaQuery.of(context).size.width * 0.3,
child: Center(
child: Text(
'Ringing...',
style: TextStyle(color: Colors.white, fontSize: 30),
),
))
],
),
),
);
}
/// Helper function to get list of native views
List<Widget> _getRenderViews() {
final List<StatefulWidget> list = [];
list.add(AgoraVideoView(
controller: VideoViewController(
rtcEngine: _engine,
canvas: const VideoCanvas(uid: 0),
)));
_users.forEach((int uid) => list.add(AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: _engine,
canvas: VideoCanvas(uid: uid),
connection: RtcConnection(channelId: channelName),
),
)));
return list;
}
/// Video view wrapper
Widget _videoView(view) {
return Expanded(child: Container(child: view));
}
/// Video view row wrapper
Widget _expandedVideoRow(List<Widget> views) {
final wrappedViews = views.map<Widget>(_videoView).toList();
return Expanded(
child: Row(
children: wrappedViews,
),
);
}
Widget _viewRows() {
final views = _getRenderViews();
switch (views.length) {
case 1:
return Container(
child: Column(
children: <Widget>[_videoView(views[0])],
));
case 2:
return Container(
child: Column(
children: <Widget>[
_expandedVideoRow([views[0]]),
_expandedVideoRow([views[1]])
],
));
case 3:
return Container(
child: Column(
children: <Widget>[
_expandedVideoRow(views.sublist(0, 2)),
_expandedVideoRow(views.sublist(2, 3))
],
));
case 4:
return Container(
child: Column(
children: <Widget>[
_expandedVideoRow(views.sublist(0, 2)),
_expandedVideoRow(views.sublist(2, 4))
],
));
default:
}
return Container();
}
Widget _toolbar() {
return Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.symmetric(vertical: 48),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RawMaterialButton(
onPressed: onOffSpeaker,
child: Icon(
isSpeakerOn ? Icons.volume_up_sharp : Icons.volume_off,
color: isSpeakerOn ? Colors.white : Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: isSpeakerOn ? Colors.blueAccent : Colors.white,
padding: const EdgeInsets.all(12.0),
),
RawMaterialButton(
onPressed: _onToggleMute,
child: Icon(
muted ? Icons.mic_off : Icons.mic,
color: muted ? Colors.white : Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: muted ? Colors.blueAccent : Colors.white,
padding: const EdgeInsets.all(12.0),
),
RawMaterialButton(
onPressed: () => _onCallEnd(context),
child: Icon(
Icons.call_end,
color: Colors.white,
size: 35.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.redAccent,
padding: const EdgeInsets.all(15.0),
),
RawMaterialButton(
onPressed: _onSwitchCamera,
child: Icon(
Icons.switch_camera,
color: Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.white,
padding: const EdgeInsets.all(12.0),
)
],
),
);
}
void _onToggleMute() {
setState(() {
muted = !muted;
});
_engine.muteLocalAudioStream(muted);
}
void _onCallEnd(BuildContext context) {
Navigator.pop(context);
}
void _onSwitchCamera() {
_engine.switchCamera();
}
Future onOffSpeaker() async {
setState(() {
isSpeakerOn = !isSpeakerOn;
});
await _engine.setEnableSpeakerphone(isSpeakerOn);
}
}
I used Logger package to view logs and I found out the error occurs in this piece of code
await _engine.initialize(RtcEngineContext(
appId: appID,
));
The app uses agora_rtc_engine: ^6.1.0 and defined in pubspec.yaml file
In Agora SDK, there must be a late field requestPort which is accessed before the initialization.
It seems already similar issue raised in Agora SDK github repo. But its closed.
You can open a new issue or reopen existing with steps to reproduce the issue.
Error : type ' () => Map<String, dynamic >' is not a subtype of type 'DocumentSnapshot<Object?>' in type cast.
Below is the code sample of my chat screen which is one of the major component of my chat appllication where I wanted to build a stream of messages using the firebase but this gives an error app works completely fine till this screen but after this screen it throws an error please help me resolve this issue , I am using Streambuilder widget , please look into it
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:lets_chat/constant.dart';
import 'package:firebase_auth/firebase_auth.dart';
//final _firestore = FirebaseFirestore.instance;
//FirebaseUser = loggedInUser;
late User loggedInUser;
class ChatScreen extends StatefulWidget {
static const String id3 = 'chat_screen';
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final messageTextController = TextEditingController();
final _fireStore = FirebaseFirestore.instance;
final _auth = FirebaseAuth.instance;
late String messageText;
#override
void initState() {
// TODO: implement initState
super.initState();
getCurrentUser();
}
void getCurrentUser() async {
try {
final user = await _auth.currentUser;
if (user != null) {
loggedInUser = user;
}
} catch (e) {
print(e);
}
}
void messagesStream() async {
await for (var snapshot in _fireStore.collection('messages').snapshots()) {
for (var messages in snapshot.docs) {
print(messages.data);
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
//Implement logout functionality
}),
],
title: Text('⚡️Chat'),
backgroundColor: Colors.lightBlueAccent,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: _fireStore.collection('messages').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final messages = snapshot.data?.docs;
List<MessageBubble> messageBubbles = [];
for (var message in messages!) {
final messageText =
(message.data as DocumentSnapshot)['text'];
final messageSender =
(message.data as DocumentSnapshot)['sender'];
final currentUser = loggedInUser.email;
final messageBubble = MessageBubble(
sender: messageSender,
text: messageText,
loggedUser: currentUser == messageSender,
);
messageBubbles.add(messageBubble);
}
return Expanded(
child: ListView(
children: messageBubbles,
),
);
},
),
Container(
decoration: kMessageContainerDecoration,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller: messageTextController,
onChanged: (value) {
messageText = value;
},
decoration: kMessageTextFieldDecoration,
),
),
TextButton(
onPressed: () {
_fireStore.collection('messages').add({
'text': messageText,
'sender': loggedInUser.email,
});
},
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
),
],
),
),
);
}
}
class MessageBubble extends StatelessWidget {
MessageBubble(
{required this.sender, required this.text, required this.loggedUser});
final String sender;
final String text;
final bool loggedUser;
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'sender',
style: TextStyle(
color: Colors.pink,
),
),
Material(
elevation: 5.0,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
bottomLeft: Radius.circular(20.0),
bottomRight: Radius.circular(20.0)),
color: loggedUser
? Colors.pinkAccent.shade200
: Colors.blueAccent.shade200,
child: Padding(
padding:
EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),
child: Text('$text from $sender'))),
],
),
);
}
}
Issue with your code is that you're trying to do message.data as DocumentSnapshot which is trying to cast () => Map<String, dynamic> (returned by .data) to a DocumentSnapshot, which is incorrect.
message is a QuerySnapshot and you need to use data() method to access the fields.
final messages = snapshot.data?.docs;
List<MessageBubble> messageBubbles = [];
for (var message in messages!) {
final messageText = message.data()['text'];
final messageSender = message.data()['sender'];
// ...
}
I am currently developing chat app, the problem is I can not take the datas from firebase, on the chat screen nothing shown as a chat, To sort my datas on firebase to take them to my chat screen like a real chat app, I used orderBy('createAt', descending: true).snapshots() property, It sort my datas on firebase according time, but before I used this parameter I could see the messages but now I can, Thanks for your answers, Have a nice day..
import 'package:flutter/material.dart';
import'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:naber/constants.dart';
final _firestore=FirebaseFirestore.instance;
User loggedInUser;
class ChatScreen extends StatefulWidget {
static String id="chat_screen";
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final messageTextController = TextEditingController();
final _auth = FirebaseAuth.instance;
String messageText;
void initState(){
super.initState();
getCurrentUser();
}
void getCurrentUser()async{
try{
final currentUser = await _auth.currentUser;
if(currentUser!=null){
loggedInUser=currentUser;
}
}
catch(e){
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
_auth.signOut();
Navigator.pop(context);
}),
],
title: Text('Chat'),
backgroundColor: Colors.lightBlueAccent,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
MessagesStream(),
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();
_firestore.collection('messages').add({
'text': messageText,
'sender': loggedInUser.email,
'createdAt': FieldValue.serverTimestamp(),
});
},
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
),
],
),
),
);
}
}
class MessagesStream extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('messages').
orderBy('createAt', descending: true).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.lightBlueAccent,
),
);
}
final messages = snapshot.data.docs.reversed;
List<MessageBubble> messageBubbles = [];
for (var message in messages) {
final messageText = message.data()['text'];
final messageSender = message.data()['sender'];
final currentUser = loggedInUser.email;
final messageBubble = MessageBubble(
sender: messageSender,
text: messageText,
isMe: currentUser == messageSender,
);
messageBubbles.add(messageBubble);
}
return Expanded(
child: ListView(
reverse: true,
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
children: messageBubbles,
),
);
},
);
}
}
class MessageBubble extends StatelessWidget {
MessageBubble({this.sender, this.text, this.isMe});
final String sender;
final String text;
final bool isMe;
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment:
isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
Text(
sender,
style: TextStyle(
fontSize: 12.0,
color: Colors.black54,
),
),
Material(
borderRadius: isMe
? BorderRadius.only(
topLeft: Radius.circular(30.0),
bottomLeft: Radius.circular(30.0),
bottomRight: Radius.circular(30.0))
: BorderRadius.only(
bottomLeft: Radius.circular(30.0),
bottomRight: Radius.circular(30.0),
topRight: Radius.circular(30.0),
),
elevation: 5.0,
color: isMe ? Colors.lightBlueAccent : Colors.white,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
child: Text(
text,
style: TextStyle(
color: isMe ? Colors.white : Colors.black54,
fontSize: 15.0,
),
),
),
),
],
),
);
}
}
[![Here is my database screenshot][1]][1]
I'm trying to build a chat application which displays time along with the message. Here is the main code:
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';
final _fireStore = Firestore.instance;
FirebaseUser loggedInUser;
class ChatScreen extends StatefulWidget {
static String chatScreen = 'ChatScreenpage1';
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final messageTextEditingController = TextEditingController();
String messageText;
final _auth = FirebaseAuth.instance;
#override
void initState() {
super.initState();
getUserDetail();
}
void getUserDetail() async {
try {
final createdUser = await _auth.currentUser();
if (createdUser != null) {
loggedInUser = createdUser;
}
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
_auth.signOut();
Navigator.pop(context);
}),
],
title: Text('⚡️Chat'),
backgroundColor: Colors.lightBlueAccent,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
StreambuilderClass(),
Container(
decoration: kMessageContainerDecoration,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller: messageTextEditingController,
onChanged: (value) {
messageText = value;
},
decoration: kMessageTextFieldDecoration,
),
),
FlatButton(
onPressed: () {
messageTextEditingController.clear();
_fireStore.collection('messages').add({
'sender': loggedInUser.email,
'text': messageText,
'time': FieldValue.serverTimestamp()
});
},
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
),
],
),
),
);
}
}
class StreambuilderClass extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _fireStore
.collection('messages')
.orderBy('time', descending: false)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.blueAccent,
),
);
}
final messages = snapshot.data.documents.reversed;
List<MessageBubble> messageBubbles = [];
for (var message in messages) {
final messageText = message.data['text'];
final messageSender = message.data['sender'];
final messageTime = message.data['time'] as Timestamp;
final currentUser = loggedInUser.email;
print('check time: $messageTime'); //print(message.data['time']); both gives null
print('check sender: $messageSender');
print('check sender: $messageText');
print(snapshot.connectionState);
final messageBubble = MessageBubble(
sender: messageSender,
text: messageText,
isMe: currentUser == messageSender,
time: messageTime,
);
messageBubbles.add(messageBubble);
}
return Expanded(
child: ListView(
reverse: true,
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 20),
children: messageBubbles),
);
});
}
}
class MessageBubble extends StatelessWidget {
final String text;
final String sender;
final bool isMe;
final Timestamp time;
MessageBubble({this.text, this.sender, this.isMe, this.time});
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment:
isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
Text(
' $sender ${DateTime.fromMillisecondsSinceEpoch(time.seconds * 1000)}',
style: TextStyle(color: Colors.black54, fontSize: 12),
),
Material(
color: isMe ? Colors.blueAccent : Colors.white,
borderRadius: isMe
? BorderRadius.only(
topLeft: Radius.circular(30),
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30))
: BorderRadius.only(
topRight: Radius.circular(30),
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30)),
elevation: 6,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
child: Text(
text,
style: TextStyle(
fontSize: 20, color: isMe ? Colors.white : Colors.black),
),
),
),
],
),
);
}
}
But I get this exception for a moment(almost a second) with a red screen and then everything works fine:
By printing the snapshot data field values(The highlighted code in the image) for like 100 times with 100 messages, I realized that the StreamBuilder is sending updated snapshot twice.
(You can see in the output that the first snapshot is with just time field being null and immediately in the second snapshot all values are being present, this happens for every new message I send.)
Everything works as expected in my other app which doesn't use timestamp field in cloud firestore.
My question is shouldn't the StreamBuilder should just send one snapshot for every one update with all the data values being present at once?
Please tell me if I've made a mistake. Any help would be really appreciated!
This is actually expected behaviour for a StreamBuilder. As you can see in this Community Answer:
StreamBuilder makes two build calls when initialized, once for the
initial data and a second time for the stream data.
Streams do not guarantee that they will send data right away so an
initial data value is required. Passing null to initialData throws an
InvalidArgument exception.
StreamBuilders will always build twice even when the stream passed is
null.
So, in order to mitigate that exception and red screen glitch, you will have to take this into consideration and treat this scenario in your code.
Everyone I am new in flutter. How can create loading screen on login page. when i click on login button then it should be redirect on Dashboard with loading screen. I am trying to create loading screen in flutter. When I click on login button then loading screen show be show and after loading screen it should be redirect on Dashboard.....
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutternew/dashboard.dart';
import 'package:flutternew/forget_password.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'bezierContainer.dart';
class Home extends StatefulWidget {
Home({Key key, this.title}) : super(key: key);
final String title;
#override
_HomeState createState() => _HomeState();
}
class ShowLoading extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text('Loading View'),
),
body: Center(
child: _circularProgressIndicator(),
));
}
Widget _circularProgressIndicator() {
return CircularProgressIndicator();
}
}
class _HomeState extends State<Home> {
bool _isLoading = false;
GlobalKey<FormState> _formKey = GlobalKey<FormState>(debugLabel: '_homekey');
final _scaffoldKey = GlobalKey<ScaffoldState>();
String _email;
String _password;
final TextEditingController emailController = new TextEditingController();
final TextEditingController passwordController = new TextEditingController();
var modal = Container();
showAlertDialog(BuildContext context, String message) {
Widget okButton = FlatButton(
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
);
AlertDialog alert = AlertDialog(
title: Text("Error"),
content: Text(message),
actions: [
okButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
Widget _formSetupWidget(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
controller: emailController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Username",
hintStyle: TextStyle(color: Colors.grey, fontSize: 25),
fillColor: Color(0xFFFAFAfA),
filled: true),
keyboardType: TextInputType.emailAddress,
validator: (val) {
if (val.length == 0)
return "Please enter email";
else if (!val.contains("#"))
return "Please enter valid email";
else
return null;
},
onSaved: (val) => _email = val,
),
SizedBox(
height: 15,
),
TextFormField(
controller: passwordController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Password",
hintStyle: TextStyle(color: Colors.grey, fontSize: 25),
fillColor: Color(0xFFFAFAfA),
filled: true),
obscureText: true,
validator: (val) {
if (val.length == 0)
return "Please enter password";
else if (val.length <= 5)
return "Your password should be more then 6 char long";
else
return null;
},
onSaved: (val) => _password = val,
),
Padding(
padding: const EdgeInsets.only(top: 20.0),
),
ButtonTheme(
minWidth: 500.0,
height: 60.0,
child: RaisedButton(
child: new Text("Login",
style: TextStyle(color: Colors.white, fontSize: 23.0)),
color: Colors.blueAccent,
highlightColor: Colors.blueAccent,
onPressed: () {
if (emailController.text != " " ||
passwordController.text != " ") {
signIn(emailController.text, passwordController.text);
}
},
),
),
],
),
);
}
signIn(String email, pass) async {
int visible = 0;
final sharedPreferences = await SharedPreferences.getInstance();
Map data = {'email': email, 'password': pass};
var jsonResponse = null;
var response = await http
.post("https://abcdef.com/iot/api/login", body: data);
if (response.statusCode == 200) {
jsonResponse = json.decode(response.body);
if (jsonResponse != null) {
if (jsonResponse['success'] == 1) {
sharedPreferences.setString("token", jsonResponse['data']['token']);
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (BuildContext context) => Dashboard()),
(Route<dynamic> route) => false);
} else {
String message = jsonResponse['message'];
showAlertDialog(context, message);
}
}
} else {
setState(() {
_isLoading = false;
});
print(response.body);
}
}
#override
Widget build(BuildContext context) {
Center(
child: SpinKitWave(color: Colors.white, type: SpinKitWaveType.start),
);
var assetsImage = new AssetImage('assets/logo.png');
var image = new Image(image: assetsImage, width: 150.0, height: 150.0);
final height = MediaQuery.of(context).size.height;
return Scaffold(
body: Container(
height: height,
color: Colors.white,
child: Stack(
children: <Widget>[
Positioned(
top: -height * .15,
right: -MediaQuery.of(context).size.width * .4,
child: BezierContainer()),
Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(height: 70),
new Center(
child: image,
),
SizedBox(height: 20),
_formSetupWidget(context),
Container(
padding: EdgeInsets.symmetric(vertical: 10),
alignment: Alignment.centerRight,
child: FlatButton(
child: Text("Forgot Password?"),
onPressed: () {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) =>
Forget_password()),
(Route<dynamic> route) => false);
},
),
),
SizedBox(height: height * .055),
],
),
),
),
],
),
));
}
}
Create class
class LoaderDialog {
static Future<void> showLoadingDialog(BuildContext context, GlobalKey key) async {
var wid = MediaQuery.of(context).size.width / 2;
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Padding(
padding: EdgeInsets.only(left: 130 , right: 130),
child: Dialog(
key: key,
backgroundColor: Colors.white,
child: Container(
width: 60.0,
height: 60.0,
child: Image.asset(
'images/loaderOld.gif',
height: 60,
width: 60,
),
)
),
);
},
);
}
}
How to Call:
In your Class(Where you want to show the loader).
final GlobalKey<State> _LoaderDialog = new GlobalKey<State>();
// Show
LoaderDialog.showLoadingDialog(context, _LoaderDialog);
// Hide
Navigator.of(_LoaderDialog.currentContext,rootNavigator: true).pop();