I am trying to make a flash Chat App that retrieves the chats from fireBase and displays it on the Screen .I have wrapped it under an Expanded widget .I have give some padding to it .
I am getting the following error
The following assertion was thrown while looking for parent data.:
Incorrect use of ParentDataWidget.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flashchat1/constants.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class ChatScreen extends StatefulWidget {
static String id='Chat_Screen';
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _fireStore = FirebaseFirestore.instance;//an instance of fireBase store that stored data created
final _auth = FirebaseAuth.instance;//instance/object of fireBase auth that authorizes users is created
late User loggedInUser;//LoggedInUser is of type FireBase user(now changed to user)
late String messageText;
#override
void initState()
{
super.initState();
getCurrentUser();//calling the getCurrentUser
}
void getCurrentUser()
async{
try
{
final user= await _auth.currentUser;//get the current user id/name/email.Also currentUser return a future so make it async by adding await and async keywords
if(user!=null)
{
loggedInUser=user ;//LoggedInUser = user contains email of the info
print(loggedInUser.email);
}
}
catch(e)
{
print(e);
}
}// Under collection there is documents.Inside documents there are fields like type ,values etc.These fields contain our information
Future<void> messageStream()//Using a stream it becomes very easy .U just need to click once after you run the app .Then u will be done.
async {//The snapShot here is FireBase's Query SnapShot
await for(var snapshot in _fireStore.collection('messages').snapshots()){//make a variable snapshot to store the entire items of the collection in fireBase (Look at the fireBase console there is a collection called messages).This collection takes the snapshot of all the iteams (not literal snapshot .Think it like a snapShot)
for(var message in snapshot.docs)//make a variable message to access the snapShot.docs .(docs stands for Documentation.Look at the fireBase console)
print(message.data());
}
}
void getMessages()//(The problem with this is that we need to keep clicking on the onPressed button every single time the new message is sent .So it is not convinient
async {
final messages = await _fireStore.collection('messages').get();//to retrieve the data from fire base we are creating a variable message
messages.docs;//retreive the data from document section under the collection in firestore
for(var message in messages.docs)//since it is a messages.docs is a list we need to loop through it
{
print(message.data());//print the data its messge.data()
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
messageStream();
//_auth.signOut();
//Navigator.pop(context);
//Implement logout functionality
}),
],
title: Text('⚡️Chat'),
backgroundColor: Colors.lightBlueAccent,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: StreamBuilder(
stream:_fireStore.collection('messages').snapshots(),
builder: (context, AsyncSnapshot snapshot) {
//This is Flutter's Async snapShot
//if(!snapshot.data)
// {
// return Center(
//child: CircularProgressIndicator(
//backgroundColor:Colors.lightBlueAccent,
//),
//);
//}
if(!snapshot.hasData){//flutters async snapshot contains a query snapshot
return Center(
child:CircularProgressIndicator(
backgroundColor:Colors.lightBlueAccent,
),
);
}
final messages = snapshot.data.docs;
List<Text> messageWidgets = [];
for(var message in messages)//Loop through the messages
{
final messageText = message.data()['text'];//retrieve the data under the text field in message collection
final messageSender = message.data()['Sender'];//retrieve the data under the Sender field in message collection
final messageWidget = Text('$messageText from $messageSender',
style:TextStyle(
fontSize:50,
),
);
messageWidgets.add(messageWidget);//add the text to the List messageWidget
}
return Expanded(
flex:2,
child: ListView(//changed from Column to ListView as we want to scroll down .Or else only finite messages can be fit
children: messageWidgets, //if u don't write else with a return it will show an error as null returned and null safety broken
padding: EdgeInsets.symmetric(horizontal: 5,vertical: 5),
),
);
},
),
),
Container(
decoration: kMessageContainerDecoration,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
onChanged: (value) {
messageText=value;//Whatever you chat will be stored in the variable String variable messageText
},
decoration: kMessageTextFieldDecoration,
),
),
FlatButton(
onPressed: () {
_fireStore.collection('messages').add({
'text': messageText,//add the messages sent to fireStore under the messages object that we created manually
'Sender': loggedInUser.email,//add the current users email to the sender field
},);
},//goal is to send the data that we type here to the fireStore cloud
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
),
],
),
),
);
}
}
return Expanded(
flex:2,
child: ListView(//changed from Column to ListView as we want to scroll down .Or else only finite messages can be fit
children: messageWidgets, //if u don't write else with a return it will show an error as null returned and null safety broken
padding: EdgeInsets.symmetric(horizontal: 5,vertical: 5),
),
);
This code block is the issue here. You cannot use Expanded widget anywhere you like. The Expanded widget can only be used inside Row or Column Widget.
Remove the Expanded widget in the above code block. It will works.
Related
I am working with Flutter sdk version 2.12.0.I am creating a chat app which can be used to chat with other users. The chat history will be stored in fireBase . I am trying to retrieve the data of what I chatted and display it on the screen using Stream Builder widget.
As i keep chatting the data should get automatically added.
I am getting the following error:
Closure call with mismatched arguments: function '[]'
Receiver: Closure: () => Map<String, dynamic> from Function 'data':.
Tried calling: []("text")
Found: []() => Map<String, dynamic>
I am not able to figure out which function has mis Matched arguments. Can you please me with it. Here is my code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flashchat1/constants.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class ChatScreen extends StatefulWidget {
static String id='Chat_Screen';
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _fireStore = FirebaseFirestore.instance;//an instance of fireBase store that stored data created
final _auth = FirebaseAuth.instance;//instance/object of fireBase auth that authorizes users is created
late User loggedInUser;//LoggedInUser is of type FireBase user(now changed to user)
late String messageText;
#override
void initState()
{
super.initState();
getCurrentUser();//calling the getCurrentUser
}
void getCurrentUser()
async{
try
{
final user= await _auth.currentUser;//get the current user id/name/email.Also currentUser return a future so make it async by adding await and async keywords
if(user!=null)
{
loggedInUser=user ;//LoggedInUser = user contains email of the info
print(loggedInUser.email);
}
}
catch(e)
{
print(e);
}
}// Under collection there is documents.Inside documents there are fields like type ,values etc.These fields contain our information
Future<void> messageStream()//Using a stream it becomes very easy .U just need to click once after you run the app .Then u will be done.
async {//The snapShot here is FireBase's Query SnapShot
await for(var snapshot in _fireStore.collection('messages').snapshots()){//make a variable snapshot to store the entire items of the collection in fireBase (Look at the fireBase console there is a collection called messages).This collection takes the snapshot of all the iteams (not literal snapshot .Think it like a snapShot)
for(var message in snapshot.docs)//make a variable message to access the snapShot.docs .(docs stands for Documentation.Look at the fireBase console)
print(message.data());
}
}
void getMessages()//(The problem with this is that we need to keep clicking on the onPressed button every single time the new message is sent .So it is not convinient
async {
final messages = await _fireStore.collection('messages').get();//to retrieve the data from fire base we are creating a variable message
messages.docs;//retreive the data from document section under the collection in firestore
for(var message in messages.docs)//since it is a messages.docs is a list we need to loop through it
{
print(message.data());//print the data its messge.data()
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
messageStream();
//_auth.signOut();
//Navigator.pop(context);
//Implement logout functionality
}),
],
title: Text('⚡️Chat'),
backgroundColor: Colors.lightBlueAccent,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: StreamBuilder(
stream:_fireStore.collection('messages').snapshots(),
builder: (context, AsyncSnapshot snapshot) {
//This is Flutter's Async snapShot
//if(!snapshot.data)
// {
// return Center(
//child: CircularProgressIndicator(
//backgroundColor:Colors.lightBlueAccent,
//),
//);
//}
if(snapshot.hasData){//flutters async snapshot contains a query snapshot
final messages = snapshot.data.docs;
List<Text> messageWidgets = [];
for(var message in messages)//Loop through the messages
{
final messageText = message.data['text'];//retrieve the data under the text field in message collection
final messageSender = message.data['Sender'];//retrieve the data under the Sender field in message collection
final messageWidget = Text('$messageText from $messageSender');
messageWidgets.add(messageWidget);//add the text to the List messageWidget
}
return Column(//
children: messageWidgets,//if u don't write else with a return it will show an error as null returned and null safety broken
);
}
else{
return Column();
}
},
),
),
Container(
decoration: kMessageContainerDecoration,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
onChanged: (value) {
messageText=value;//Whatever you chat will be stored in the variable String variable messageText
},
decoration: kMessageTextFieldDecoration,
),
),
FlatButton(
onPressed: () {
_fireStore.collection('messages').add({
'text': messageText,//add the messages sent to fireStore under the messages object that we created manually
'Sender': loggedInUser.email,//add the current users email to the sender field
},);
},//goal is to send the data that we type here to the fireStore cloud
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
),
],
),
),
);
}
}
Change this:
final messageText = message.data['text'];
final messageSender = message.data['Sender'];
into this:
final messageText = message.data()['text'];
final messageSender = message.data()['Sender'];
I made a floatingactionbutton and every time you press it it adds an item, and each item has a checkbox next to it but when I check off one item it checks all of them, I've spent a lot of time trying to figure out how to fix this but I can't. I could really use your help.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(FireApp());
}
class FireApp extends StatefulWidget {
#override
_FireAppState createState() => _FireAppState();
}
bool isChecked = false;
class _FireAppState extends State<FireApp> {
final TextController = TextEditingController();
#override
Widget build(BuildContext context) {
CollectionReference groceries =
FirebaseFirestore.instance.collection('groceries');
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: TextField(
controller: TextController,
),
),
body: Center(
child: StreamBuilder(
stream: groceries.orderBy('name').snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
return ListView(
children: snapshot.data!.docs.map((grocery) {
return Center(
child: Row(
children: [
Container(color: Colors.red,height: 50,child: Text(grocery['name'])),
Checkbox(
materialTapTargetSize: MaterialTapTargetSize.padded,
value: isChecked,
activeColor: Colors.black,
checkColor: Colors.greenAccent,
onChanged: (bool) {
setState(() {
isChecked = !isChecked;
});
}
)],
),
);
}).toList(),
);
},
),
),
floatingActionButton: FloatingActionButton(onPressed: () {
groceries.add({
'name': TextController.text,
});
},),
),
);
}
}
You are using the same variable for all your checkboxes (isChecked) but you ougth to have one per data, you could add that attribute to your firebase document so its synced or you could create it locally but each time your stream updates you will need to compare what grocery correspond to a checkbox value which can be hard.
UPDATE
The easiest way is to have a bool parameter in your Firestore document
Then just push an update any time the user tap
return ListView(
children: snapshot.data!.docs.map((grocery) {
return Center(
child: Row(
children: [
Container(color: Colors.red,height: 50,child: Text(grocery['name'])),
Checkbox(
materialTapTargetSize: MaterialTapTargetSize.padded,
value: grocery['checked'],
activeColor: Colors.black,
checkColor: Colors.greenAccent,
onChanged: (val) async {
final data = grocery.data();
data['checked'] = val;
await grocery.reference.update(data);
}
)],
),
);
}).toList(),
);
For now this is sufficient to answer your question, you will see later that this incurs in more Firestore calls, unnecesary rebuild of all widgets in the list and so on and you will have to think another way to optimize resources, like watching the stream somewhere else to have a local List of bools that keeps in sync all values of the groceries so you only update locally with an setState and once in the cloud at the end (a save button perhaps)
I am trying to get user avatar from firebase storage, however, my current code only returns Instance of 'Future<String>' even I am using async/await as below. How is it possible to get actual download URL as String, rather Instance of Future so I can access the data from CachedNewtworkImage?
this is the function that calls getAvatarDownloadUrl with current passed firebase user instance.
myViewModel
FutureOr<String> getAvatarUrl(User user) async {
var snapshot = await _ref
.read(firebaseStoreRepositoryProvider)
.getAvatarDownloadUrl(user.code);
if (snapshot != null) {
print("avatar url: $snapshot");
}
return snapshot;
}
getAvatarURL is basically first calling firebase firestore reference then try to access to the downloadURL, if there is no user data, simply returns null.
Future<String> getAvatarDownloadUrl(String code) async {
Reference _ref =
storage.ref().child("users").child(code).child("asset.jpeg");
try {
String url = await _ref.getDownloadURL();
return url;
} on FirebaseException catch (e) {
print(e.code);
return null;
}
}
I am calling these function from HookWidget called ShowAvatar.
To show current user avatar, I use useProvider and useFuture to actually use the data from the database, and this code works with no problem.
However, once I want to get downloardURL from list of users (inside of ListView using index),
class ShowAvatar extends HookWidget {
// some constructors...
#override
Widget build(BuildContext context) {
// get firebase user instance
final user = useProvider(accountProvider.state).user;
// get user avatar data as Future<String>
final userLogo = useProvider(firebaseStoreRepositoryProvider)
.getAvatarDownloadUrl(user.code);
// get actual user data as String
final snapshot = useFuture(userLogo);
// to access above functions inside of ListView
final viewModel = useProvider(myViewModel);
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Container(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 100,
width: 100,
child: Avatar(
avatarUrl: snapshot.data, // **this avatar works!!!** so useProvider & useFuture is working
),
),
SizedBox(height: 32),
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return Center(
child: Column(
children: [
SizedBox(
height: 100,
width: 100,
child: Avatar(
avatarUrl: viewModel
.getAvatarUrl(goldWinners[index].user)
.toString(), // ** this avatar data is not String but Instance of Future<String>
),
),
),
],
),
);
},
itemCount: goldWinners.length,
),
Avatar() is simple statelesswidget which returns ClipRRect if avatarURL is not existed (null), it returns simplace placeholder otherwise returns user avatar that we just get from firebase storage.
However, since users from ListView's avatarUrl is Instance of Future<String> I can't correctly show user avatar.
I tried to convert the instance to String multiple times by adding .toString(), but it didn't work.
class Avatar extends StatelessWidget {
final String avatarUrl;
final double radius;
final BoxFit fit;
Avatar({Key key, this.avatarUrl, this.radius = 16, this.fit})
: super(key: key);
#override
Widget build(BuildContext context) {
print('this is avatar url : ' + avatarUrl.toString());
return avatarUrl == null
? ClipRRect(
borderRadius: BorderRadius.circular(radius),
child: Image.asset(
"assets/images/avatar_placeholder.png",
fit: fit,
),
)
: ClipRRect(
borderRadius: BorderRadius.circular(radius),
child: CachedNetworkImage(
imageUrl: avatarUrl.toString(),
placeholder: (_, url) => Skeleton(radius: radius),
errorWidget: (_, url, error) => Icon(Icons.error),
fit: fit,
));
}
}
Since the download URL is asynchronously determined, it is returned as Future<String> from your getAvatarUrl method. To display a value from a Future, use a FutureBuilder widget like this:
child: FutureBuilder<String>(
future: viewModel.getAvatarUrl(goldWinners[index].user),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
return snapshot.hashData
? Avatar(avatarUrl: snapshot.data)
: Text("Loading URL...")
}
)
Frank actually you gave an good start but there are some improvements we can do to handle the errors properly,
new FutureBuilder(
future: //future you need to pass,
builder: (context, snapshot) {
if (snapshot.hasData) {
return new ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, i) {
DocumentSnapshot ds = snapshot.data.docs[i];
return //the data you need to return using /*ds.data()['field value of doc']*/
});
} else if (snapshot.hasError) {
// Handle the error and stop rendering
GToast(
message:
'Error while fetching data : ${snapshot.error}',
type: true)
.toast();
return new Center(
child: new CircularProgressIndicator(),
);
} else {
// Wait for the data to fecth
return new Center(
child: new CircularProgressIndicator(),
);
}
}),
Now if you are using a text widget as a return statement in case of errors it will be rendered forever. Incase of Progress Indicators, you will exactly know if it is an error it will show the progress indicator and then stop the widget rendering.
else if (snapshot.hasError) {
}
else {
}
above statement renders until, if there is an error or the builder finished fetching the results and ready to show the result widget.
I want to retrieve a particular user document by its id from the collection users. When I directly pass the particular user id, I get the data. But when I pass it using variable it shows null.
My code is as follows:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../services/crud.dart';
class test extends StatefulWidget {
#override
_testState createState() => _testState();
}
class _testState extends State<test> {
String userID="";
#override
void initState() {
super.initState();
///get current user and assign his id
FirebaseAuth.instance.currentUser().then((FirebaseUser user) {
setState(() {
userID = user.uid;
print(userID);
});
});
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
StreamBuilder(
stream: Firestore.instance.collection('users').document(userID).snapshots(),
builder: (context,snapshot){
if (!snapshot.hasData) return const Text("Loading...");
else return Container(
child: Column(
children: <Widget>[
Text(snapshot.data["name"]),
Text(snapshot.data["email"]),
Text(snapshot.data["phone"].toString()),
],
),
);
},
)
],
)
);
}
}
When I'm using the following line of code with specifying uid it shows the result :
stream: Firestore.instance.collection('users').document('d6DshRomJMkIe9mAARAi').snapshots(),
But it does not work when I pass userID inside the document(). Even though userID contains the actual id of the logged-in user.
stream: Firestore.instance.collection('users').document(userID).snapshots(),
The error says :
NoSuchMethodError: The method '[]' was called on null.
Receiver: null
Tried calling []("name")
This is the structure of my database.
This is the error that I get on my app screen.
When using userID is doesn't work because currentUser() is asynchronous and the StreamBuilder is being called even before getting the userId therefore try the following:
Stream<DocumentSnapshot> getData()async*{
FirebaseUser user = await FirebaseAuth.instance.currentUser();
yield* Firestore.instance.collection('users').document(user.uid).snapshots();
}
Create a method that returns a Stream and then inside the StreamBuilder do the following:
children: <Widget>[
StreamBuilder(
stream: getData(),
builder: (context,snapshot){
if (!snapshot.hasData) return const Text("Loading...");
else if(snapshot.hasData){
return Container(
child: Column(
children: <Widget>[
Text(snapshot.data["name"]),
Text(snapshot.data["email"]),
Text(snapshot.data["phone"].toString()),
],
),
);
},
return CircularProgressIndicator();
},
)
],
I want to save a value from my Cloud Firestore as a string. I am using Flutter with Dart. I have been able to save it when building the page using MaterialepageRoute:
MaterialPageRoute(
builder: (context) => MainScreen(
currentUserId: firebaseUser.uid,
currentUserGender: document['gender'],
currentUserPreference: document['preference'],
)),
But this isn't an option with all of my pages, so I have to look for something else. I want to get the value from my Firestore Database, and then save it as a string, since I want to:
if (currentUserGender == 'male') {
//then do something
}
I have no idea how to do this, I have thought about using a Class, maybe the "get"-function with Firebase, but none have worked. I am not really sure how to do this, so any help is appreciated. I am able to get the currentUser. Here is a picture of my database:
https://imgur.com/KL7HX6P
Thanks in advance.
A Minimal Example: To fetch a Single Document Fields. Swap Collection & Document name in the code with your Own Names.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class GetUser extends StatefulWidget {
#override
_GetUserState createState() => _GetUserState();
}
class _GetUserState extends State<GetUser> {
Map<String, dynamic> userDetails = {};
Future<Null> getUser() async {
await Firestore.instance
.collection('users') // Your Collections Name
.document('eMAE4XF9cTYS12MpfOuWBW4P2WH3') // Your user Document Name
.get()
.then((val) {
userDetails.addAll(val.data);
}).whenComplete(() {
print('Data Fetched');
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
textColor: Colors.white,
color: Theme.of(context).accentColor,
onPressed: () async {
await getUser();
},
child: Text('Get User Detail from Cloud'),
),
userDetails.length > 0
? Column(
children: <Widget>[
Text('${userDetails['gender']}'),
Text('${userDetails['id']}'),
Text('${userDetails['nickname']}'),
userDetails['gender'] == 'male'
? Text('Its Boy')
: Text('Girl'),
],
)
: Text('No user Data, Please Fetch'),
],
),
),
);
}
}