How can i compare fields in firebase with flutter? - firebase

I'm building a chat app and I'm trying to build a screen with the user history conversations with other users.
So basically what I am trying to do is taking all the values in the message field map (just the values, not the keys) like shown here:
And I want to compare it with the docs in the chatRoom like shown here:
If I compare them and the result came as true, I want to show the user the data that inside that chatRoom.
I'm really struggling to understand how to compare them
Thanks in advance.
Edit: here is the code:
final String chatId;
Chat({this.chatId});
#override
_ChatState createState() => _ChatState();
}
class _ChatState extends State<Chat> {
// Our own id
final String currentUserId = auth.currentUser?.uid;
// Loading state
bool isLoading = false;
// Query variable
Future<QuerySnapshot> chatHistoryResults;
// All the user rooms will be in this variable
List<String> roomsUserList = [];
// All the chat rooms will be in this variable
List<String> roomsChatList = [];
// All the history chat tickets will be here.
List<QuerySnapshot> tickets = [];
// Getting all the current user rooms in a list of Id rooms.
//TODO: fix the chat history
getRooms() async {
roomsUserList = [];
//roomsChatList = [];
DocumentSnapshot myDocUser = await userRef.doc(currentUserId).get();
Map<dynamic, dynamic> userRoomsMap = await myDocUser.get('messages');
userRoomsMap.forEach((key, value) {
roomsUserList.add(value);
});
print(roomsUserList);
roomsUserList.forEach((element) async {
String chatDoc = chatRef.doc(element).get().toString();
roomsChatList.add(chatDoc.toString());
});
print(roomsChatList);
}
// Build the story tickets here
buildStoryTickets() {
return FutureBuilder(
future: chatRef.get().then((snapshot) {
snapshot.docs.forEach((element) {});
}),
);
}
#override
Widget build(BuildContext context) {
return Provider<AuthService>(
create: (context) => AuthService(),
child: Scaffold(
backgroundColor: Colors.grey[350],
appBar: PreferredSize(
preferredSize: Size.fromHeight(55.0),
child: Header(
title: 'Chat',
),
),
body: ListView(
children: [
//buildStoryTickets(),
Container(
height: 100,
width: 100,
child: FlatButton(onPressed: () {
getRooms(); // Just to check
}),
),
],
),
),
);
}
}
I manage to insert all the values from the "messages" map inside a List called "roomUserList"

First, you can get the all the data from the User and chat room Using the StreamBuilder.
Once you have the messages and Chat room you can compare it by developing code logic.
PFB sample code to get data from firestore:
StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('users').snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
//add your comparison logic here
);
}

Related

Error while retrieving data from FireBase into flutter project

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'];

Can't get actual String download url from Firebase Storage and only returns Instance of 'Future<String>' even using async/await

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.

How to get the related image from Firebase storage in flutter

I'm trying to build a list of widgets that are displayed using streambuilder for each entry in my cloud firestore. Here's the code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ProperHomeScreen extends StatefulWidget {
#override
_ProperHomeScreenState createState() => _ProperHomeScreenState();
}
class _ProperHomeScreenState extends State<ProperHomeScreen> {
final _firestore = Firestore.instance;
String _downloadURL;
StorageReference _reference = FirebaseStorage.instance.ref();
#override
void initState() {
super.initState();
}
void postsStream() async {
await for (var snapshot in _firestore.collection('posts').snapshots()) {
for (var post in snapshot.documents) {
print(post.data);
}
}
}
testFunction(postImage) async {
print('Here\'s the postImage data from test function: $postImage');
String downloadAddress = await _reference.child(postImage).getDownloadURL();
setState(() {
_downloadURL = downloadAddress;
});
print('THIS IS THE DOWNLOAD URL FROM THE TEST FUNCTION! ::: $_downloadURL');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
StreamBuilder<QuerySnapshot> (
stream: _firestore.collection('posts').snapshots(),
builder: (context, snapshot) {
if(!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.lightBlueAccent,
),
);
}
final posts = snapshot.data.documents;
List<Widget> postWidgets = [];
for (var post in posts) {
final postText = post.data['questionOne'];
final postSender = post.data['email'];
final postImage = post.data['filePath'];
testFunction(postImage);
print('THIS IS THE DOWNLOAD ADDRESS : $_downloadURL');
final postWidget = Container(
child: Column(
children: <Widget>[
Text('$postText from $postSender with image : $postImage'),
Image.network('$_downloadURL'),
],
),
);
postWidgets.add(postWidget);
}
return Column(
children: postWidgets,
);
},
),
],
),
);
}
}
In the console, it is printing urls fine, but the problem I have is that it keeps running the testFunction() continuously until I stop main.dart.
I'm trying to show a different image for each post.
Essentially, I am saving data in cloud firestore and saving images in firebase storage. I'm storing the file name of the image in cloud firestore so that I can access it.
Here's a sample of how I'm saving a post in firestore:
void submitPostSection() {
DateTime now = DateTime.now();
_firestore.collection('posts').add({
'email': loggedInUser.email,
'date': now,
'questionOne': widget.questionOne, //this is a simple string. Example data: 'Here is the latest post today 31st July 2020'
'filePath' : _filePath, // this is just the image name that its saved as in firebase storage. datatype for this is string. here's an example of the data: 'myimage2.jpg'
});
}
I think the problem is because the method keeps getting called and setting state of _downloadURL. I'm not really sure the best way to go about this.
Any ideas?
Thanks in advance!
The problem is that inside testFunction() you are calling setState() which will keep calling the build() method, you can do the following:
List<String> listOfUrl = [];
for (var post in posts) {
final postText = post.data['questionOne'];
final postSender = post.data['email'];
final postImage = post.data['filePath'];
String downloadAddress = await _reference.child(postImage).getDownloadURL();
listOfUrl.add(downloadAddress);
}
ListView.builder(
shrinkWrap: true,
itemCount: listOfUrl.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.network(listOfUrl[index]),
],
),
);
});
add the urls inside a list and then use a listview to display them.
I've solved my problem. I deleted the testFunction() and just saved the actual imageURL inside the cloud firestore document. Then I can access it really easily by adding the following line:
final postImageUrl = post.data['imageURL'];

Flutter Error: type 'Future<dynamic>' is not a subtype of type 'List<Game>'

I am trying to build an app with different lists of games. As a backend I use Firebase and the connection is working fine, I tested it. Anyway I have problems with replacing the mock data with real data from firebase. I always get this error:
type 'Future < dynamic>' is not a subtype of type 'List < Game>'
I have following function:
getGames() async{
List newGamesList = [];
QuerySnapshot result = awaitFirestore.instance.collection('products').getDocuments();
List<DocumentSnapshot> documents = result.documents;
documents.forEach((DocumentSnapshot doc) {
Game game = new Game.fromDocument(doc);
newGamesList.add(game);
});
}
"Game" looks like that:
factory Game.fromDocument(DocumentSnapshot document) {
return new Game(
name: document['name'],
box: document['box'],
cover: document['cover'],
description: document['description'],
);
}
In my build widget I call "getGames":
new HorizontalGameController(getGames()),
Any idea why this error occures and how to solve that?
EDIT:
For better understanding here is my HorizontalGameController:
class HorizontalGameController extends StatelessWidget {
HorizontalGameController(this.gameItems);
final List<Game> gameItems;
#override
Widget build(BuildContext context) {
return new SizedBox.fromSize(
size: const Size.fromHeight(240.0),
child: new ListView.builder(
itemCount: 1,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.only(left: 12.0, top: 4.0),
itemBuilder: (BuildContext context, int position) {
return GameContainerItem(context, gameItems[position]);
}),
);
}
}
getGames is not returning the gameList you created. Make the function return the list of games. I can't test it, but give this a try
Future<List<Game>> getGames() async{
List<Game> newGamesList = [];
QuerySnapshot result = await Firestore.instance.collection('products').getDocuments();
List<DocumentSnapshot> documents = result.documents;
documents.forEach((DocumentSnapshot doc) {
Game game = new Game.fromDocument(doc);
newGamesList.add(game);
});
return newGamesList;
}
//then this
//new HorizontalGameController(await getGames()) //change this
EDIT
Change new HorizontalGameController(await getGames()) to the code below (wrap it with a futureBuilder). This will enable the widget make use of the future value.
FutureBuilder<List<Game>>(
future: getGames(),
builder: (context, AsyncSnapshot<List<Game>> gamelistSnapshot){
return (gamelistSnapshot.hasData)? HorizontalGameController(gamelistSnapshot.data) : Container();
},
)

How do I use an async method to build a List Object?

I am getting an error that says that the method .length is calling on a null object _genreList.
I am using an async method to get data from a local asset sqlite database to which is a list of genre's. Which then I use ListView.builder in order to display that list on the screen. This is the code to obtain the data...
Future getGenreData() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "asset_sample_sqlite.db");
ByteData data = await rootBundle.load(join("assets", "sample_sqlite.db"));
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await new File(path).writeAsBytes(bytes);
Database db = await openDatabase(path);
_genreList = await db.rawQuery('SELECT genre_name[] FROM tbl_genres');
print(_genreList);
await db.close();
}
How do I use this method inside the build Widget method so that I can access the _genreList when I use ListView.builder? like so..
#override
Widget build(BuildContext context) {
return Scaffold(
body: new ListView.builder(
itemCount: _genreList.length, //need to access the genreList here
itemBuilder: (BuildContext context, int index) {
return new Card(
child: new ListTile(
title: new Text("${_genreList[index]}"),
onTap: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => BookPage(id: index),
),
);
}
),
);
}
),
);
}
The end goal here is to display a list of genres (from the tbl_genres in my sqlite database) that will be able to pass through data to the next page to show a list of books (from the tbl_books in my sqlite database) related to that genre.
The whole point of programming asynchronously is that your user interface can stay alive while you are doing time consuming work in the background. So you need (and want) to display something like a CircularProgressIndicator or even a blank page (e.g. a Container), while the application is loading.
There are at least these two ways of doing that:
Make the widget stateful and introduce a state field loading, that you initialize to true and set to false when your data (in another field) is ready. Your code would look like that:
import 'package:flutter/material.dart';
class GenresPage extends StatefulWidget {
#override
_GenresPageState createState() => _GenresPageState();
}
class _GenresPageState extends State<GenresPage> {
bool loading;
List<String> genreNames;
#override
void initState() {
super.initState();
loading = true;
getGenreData();
}
Future getGenreData() async {
final genreData = await actuallyGetThoseNames();
setState(() {
genreNames = genreData;
loading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: !loading ? new ListView.builder(
itemCount: genreNames.length,
itemBuilder: (context, index) {
return new Card(
child: new ListTile(
title: new Text("${genreNames[index]}"),
),
);
},
) : CircularProgressIndicator(), // or Container()
);
}
}
Use a FutureBuilder. Therefore you would need to refactor your getGenreData method to return the list as a Future<List<String>>.

Resources