Update Button Color in ListView based on FireBase entry - firebase

I have the following problem and just cannot solve it... I am building some kind of question/answer (Yes/No) app and want to achieve that if a button was pressed (an answer was given) the button stays highlighted with a color. So if a user goes back to a previous button he can see which answer he gave. Right now it is like that, all the questions come out of firebase with the structure
final CollectionReference _questionsCollectionReference =
FirebaseFirestore.instance
.collection("content")
.doc(content)
.collection("block")
.doc(block)
.collection("questions");
If a user answers a question it will get saved in his User Profile under
final firestoreInstance = FirebaseFirestore.instance;
await firestoreInstance
.collection("users")
.doc(user!.id)
.collection("content")
.doc(content)
.collection("block")
.doc(block)
.collection("questions")
.doc(question)
.set({
"answer": answer, //FieldValue.arrayUnion([someData]),
}).then((_) {
print("success!");
});
Now it should basically be like, if the answer in the user path == "yes", colorize the "yes" button.
the question_view.dart
Here we build the view with a PageViewBuilder and give it a QuestionItem
import 'package:fbapp/ui/shared/ui_helpers.dart';
import 'package:fbapp/ui/widgets/question_item.dart';
import 'package:fbapp/viewmodels/questions_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:stacked/stacked.dart';
class QuestionsView extends StatelessWidget {
final String block;
final String content;
const QuestionsView({Key? key, required this.block, required this.content})
: super(key: key);
#override
Widget build(BuildContext context) {
return ViewModelBuilder<QuestionsViewModel>.reactive(
viewModelBuilder: () => QuestionsViewModel(),
onModelReady: (model) => model.fetchPosts(content, block),
builder: (context, model, child) => Scaffold(
backgroundColor: Colors.white,
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
verticalSpace(35),
Row(
children: <Widget>[
SizedBox(
height: 80,
child: Image.asset('assets/images/logo.png'),
),
],
),
Expanded(
child: model.questions != null
? Center(
child: Container(
width: 700,
height: 450,
child: PageView.builder(
controller: model.getPageController(),
scrollDirection: Axis.vertical,
itemCount: model.questions!.length,
itemBuilder: (context, index) =>
QuestionItem(
question: model.questions![index],
content: content,
block: block,
nextPage: model.nextPage,
saveCurrentUserAnswer:
model.saveCurrentUserAnswer,
getCurrentUserAnswer:
model.getCurrentUserAnswer),
),
),
)
: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(
Theme.of(context).primaryColor),
),
))
],
),
),
));
}
}
question_item.dart
import 'package:fbapp/app/app.locator.dart';
import 'package:fbapp/models/question.dart';
import 'package:flutter/material.dart';
import 'package:stacked_services/stacked_services.dart';
class QuestionItem extends StatelessWidget {
final Question? question;
final String? content;
final String? block;
final String? id;
final void Function()? nextPage;
final Future Function(
String content, String block, String? the question, String answer)?
saveCurrentUserAnswer;
final Future Function(String content, String block, String? question)?
getCurrentUserAnswer;
const QuestionItem(
{Key? key,
this.question,
this.nextPage,
this.saveCurrentUserAnswer,
this.content,
this.block,
this.id,
this.getCurrentUserAnswer})
: super(key: key);
#override
Widget build(BuildContext context) {
return Flex(
direction: Axis.horizontal,
children: [
Expanded(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width * 0.6,
child: ListTile(
leading: Icon(Icons.security),
trailing: IconButton(
icon: Icon(Icons.info),
onPressed: () {
final DialogService _dialogService =
locator<DialogService>();
_dialogService.showDialog(
dialogPlatform: DialogPlatform.Material,
title: "Info",
description: question!.info);
},
),
subtitle: Text("some nice text"),
title: Text(question!.q!),
),
),
const SizedBox(height: 50),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
margin: EdgeInsets.all(10),
height: 50.0,
child: SizedBox.fromSize(
size: Size(50, 50), // button width and height
child: ClipOval(
child: Material(
color: "yes" ==
getCurrentUserAnswer!(
content!, block!, question!.id)
.toString()
? Color.fromRGBO(0, 144, 132, 1)
: Colors.grey, // button color
child: InkWell(
splashColor: Color.fromRGBO(0, 144, 132, 1),
// splash color
onTap: () {
nextPage!();
saveCurrentUserAnswer!(
content!, block!, question!.id, "yes");
},
// button pressed
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.thumb_up,
color: Colors.white,
), // icon
Text(
"Yes",
style: TextStyle(
fontSize: 15,
color: Colors.white,
),
), // text
],
),
),
),
),
),
),
const SizedBox(width: 100, height: 100),
Container(
margin: EdgeInsets.all(10),
height: 50.0,
child: SizedBox.fromSize(
size: Size(50, 50), // button width and height
child: ClipOval(
child: Material(
color: "no" ==
getCurrentUserAnswer!(
content!, block!, question!.id)
.toString()
? Color.fromRGBO(0, 144, 132, 1)
: Colors.grey, // button colorr
child: InkWell(
splashColor: Color.fromRGBO(0, 144, 132, 1),
// splash color
onTap: () {
nextPage!();
saveCurrentUserAnswer!(
content!, block!, question!.id, "no");
},
// button pressed
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.thumb_down,
color: Colors.white,
), // icon
Text(
"No",
style: TextStyle(
fontSize: 15,
color: Colors.white,
),
), // text
],
),
),
),
),
),
),
],
)
],
),
),
),
],
);
}
}
That's how I build my page with the "yes" and "no" buttons.
This is the question_view_model.dart where we reference the firebase functions to save and get answers like so:
...
Future saveCurrentUserAnswer(
String content, String block, String? question, String answer) async {
await _fireStoreService!.saveCurrentUserAnswer(
_authenticationService!.currentUser, content, block, question, answer);
}
Future getCurrentUserAnswer(
String content, String block, String? question) async {
await _fireStoreService!.getCurrentUserAnswer(
_authenticationService!.currentUser, content, block, question);
}
...
and the firebase functions to do it:
...
Future saveCurrentUserAnswer(User? user, String content, String block,
String? question, String answer) async {
final firestoreInstance = FirebaseFirestore.instance;
await firestoreInstance
.collection("users")
.doc(user!.id)
.collection("content")
.doc(content)
.collection("block")
.doc(block)
.collection("questions")
.doc(question)
.set({
"answer": answer, //FieldValue.arrayUnion([someData]),
}).then((_) {
print("success!");
});
}
Future<String> getCurrentUserAnswer(
User? user, String content, String block, String? question) async {
String answer = "";
try {
final DocumentReference _answerCollectionReference = FirebaseFirestore
.instance
.collection("users")
.doc(user!.id)
.collection("content")
.doc(content)
.collection("block")
.doc(block)
.collection("questions")
.doc(question);
var answerDocumentSnapshot = await _answerCollectionReference;
await answerDocumentSnapshot.get().then((a) {
if (a.exists) {
answer = a["answer"];
} else {
answer = "";
}
});
print("Answer: $answer");
return answer;
} catch (e) {
return e.toString();
}
}
...
The getCurrentUserAnswer prints successfully the answer (always 2 times, not sure why...)
Cont: 00_DSGVO -- Block: b1
2
Answer: no
2
Answer: yes
success!
But the button never changes the color. I also tried it with Stateful and setState and Stateless and ValueNotifier, but somehow it doesn't work. One problem is that it have to check first if there is an answer, than rebuild the UI (or only the button) but it is building it first and afterwards checking for the answer...

I figured it out with some help, the answer was to use a FuturBuilder to wrap the question_item and set the future to getCurrentUserAnswer.
return FutureBuilder(
future: getCurrentUserAnswer!(content!, block!, question!.id),
builder: (BuildContext context, AsyncSnapshot snapshot) { return Flex(...

Related

Is there a way to compare two list and check for the value that are the same

I am create a social media type app I want to create a follower and following list like Instagram, I want when I go on some else profile and click on their followers or following list it shows a list of all the users that is following that person and if I am also following someone in the list it give me the option to unfollow that person and if I am not following the same person it gives me the option to follow them.
The code below is what I was to check if I am following any one in profile user followers list, What I have done is I query the data from firestore of all the user id the I am following and the same for user profile that is currently open and store the list in two separate variable and what it should do is check both list and if a user Id is in both list it means I am also following that user and should show that I am following that user and give the option to unfollow but what happens is instead of showing that I am only following the user who's id is in both list it show that I am following every one.
checkfollowers(BuildContext context, String? id) async {
final auth = Provider.of<AuthState>(context, listen: false);
List<String> followingList = [];
List<String> myFollowingList = [];
try {
final QuerySnapshot following = await _firestore
.collection('following')
.doc(auth.getCurrentUser.uid)
.collection('userFollowing')
.get();
QuerySnapshot userfollowing = await _firestore
.collection('followers')
.doc(id)
.collection('userFollowers')
.get();
following.docs.forEach((element) {
myFollowingList.add(element.id);
});
userfollowing.docs.forEach((element) {
followingList.add(element.id);
});
// followingList.where((item) => myFollowingList.contains(item));
check(value) => myFollowingList.contains(value);
isFollowedByMe = followingList.any(check);
notifyListeners();
print(followingList);
print(myFollowingList);
} catch (err) {
print(err.toString() + 'this error is coming from profileState');
}
}
below code is how I build the follower/following list
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
UserModel? users;
#override
void initState() {
final profileState = Provider.of<ProfileState>(context, listen: false);
profileState.checkfollowers(context, widget.proFileId);
super.initState();
}
userComponent(UserModel? model, BuildContext context) {
final profileState = Provider.of<ProfileState>(context);
final auth = Provider.of<AuthState>(context);
return Container(
margin: EdgeInsets.symmetric(horizontal: 10),
padding: EdgeInsets.only(top: 10, bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ProfilePage(profileId: '${model?.userId}'),
),
);
},
child: Row(
children: [
Container(
width: 60,
height: 60,
child: CircleAvatar(
radius: 50,
backgroundImage: NetworkImage('${model?.profilePic}'),
),
),
SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text('${model?.userName}',
style: TextStyle(
color: Colors.black, fontWeight: FontWeight.w500)),
SizedBox(
height: 5,
),
Text(
'${model?.displayName}',
style: TextStyle(
color: Colors.grey[500],
),
),
],
)
],
),
),
auth.getCurrentUser.uid == model?.userId
? Container()
: GestureDetector(
onTap: () {
if (auth.getCurrentUser.uid == model?.userId) {
print('you can not follow your self');
} else if (profileState.isFollowedByMe == true) {
profileState.setIsFollowedByMeToFalse();
profileState.handleUnFollow(context, model?.userId);
} else if (profileState.isFollowedByMe == false) {
profileState.setIsFollowedByMeToTrue();
profileState.handleFollow(context, model?.userId);
}
},
child: AnimatedContainer(
height: 35,
width: 110,
duration: Duration(milliseconds: 300),
decoration: BoxDecoration(
color: profileState.isFollowedByMe == true
? AppColors.white
: AppColors.pinkaccent,
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: Colors.grey.shade700,
),
),
child: Center(
child: Text(
profileState.isFollowedByMe == true
? 'UnFollow'
: 'Follow',
style: TextStyle(
color: profileState.isFollowedByMe == true
? Colors.black
: Colors.white),
),
),
),
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
title: Container(
height: 38,
child: TextField(
onChanged: (value) {},
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
contentPadding: EdgeInsets.all(0),
prefixIcon: Icon(
Icons.search,
color: Colors.grey.shade500,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50),
borderSide: BorderSide.none),
hintStyle: TextStyle(fontSize: 14, color: Colors.grey.shade500),
hintText: "Search users"),
),
),
),
body: StreamListWrapper(
stream: _firestore
.collection('followers')
.doc(widget.proFileId)
.collection('userFollowers')
.snapshots(),
itemBuilder: (context, DocumentSnapshot snapshot) {
var data = snapshot.data() as Map<String, dynamic>;
users = UserModel.fromJson(data);
return userComponent(users, context);
},
text: '${widget.user?.userName} as no Followers',
),
);
It would be simpler (and more performant) to use the Set data structure rather than List. The intersection method on Set returns the items contained in both sets.
for example:
void main() {
Set<String> followingSet = {'Jeff', 'Mike', 'Joe', 'Jess'};
Set<String> myFollowingSet = {'Jess', 'Matt', 'Mike', 'Frank'};
Set<String> usersInBothSets = followingSet.intersection(myFollowingSet);
print(usersInBothSets); // {Mike, Jess}
}

Still raise "firebase_auth/email-already-in-use" exception, although registered an unique email in firebase_auth flutter

I have a registration state that would handled registration of firebase authentication. From my code, there are two widgets. First (registerForm(context)), it shows registration forms. When "Register" Elevated Button is pressed, they will changed to second widgets (registerProcess(context)) there are processed a registrations.
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:connectivity/connectivity.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:path_provider/path_provider.dart';
class SignUp extends StatefulWidget {
static const routeName = "/signUp";
#override
_SignUpState createState() => _SignUpState();
}
class _SignUpState extends State<SignUp> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
final _nameController = TextEditingController();
final auth = FirebaseAuth.instance;
final fireStore = FirebaseFirestore.instance;
final storage = FirebaseStorage.instance;
bool isConnected = false;
bool passwordNotMatch = false;
bool networkWarning = false;
bool emailWarning = false;
bool isSignedUp;
#override
void initState() {
super.initState();
isSignedUp = false;
checkConnectivity();
}
Future<void> checkConnectivity() async {
var conRes = await Connectivity().checkConnectivity();
if (conRes != ConnectivityResult.none) {
isConnected = true;
}
}
Future<UserCredential> _signUp() async {
final validate = await auth.createUserWithEmailAndPassword(
email: _emailController.text,
password: _passwordController.text);
await auth.currentUser.sendEmailVerification();
final file = await getImgFromAssets("default.png");
await storage.ref("profile/${_emailController.text}.png").putFile(file);
return validate;
}
Future<File> getImgFromAssets(String path) async {
final byteData = await rootBundle.load('assets/$path');
final file = File('${(await getTemporaryDirectory()).path}/$path');
await file.writeAsBytes(byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
return file;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: isSignedUp ? registerProcess(context) : registerForm(context));
}
Widget registerForm(BuildContext context) {
double height = MediaQuery.of(context).size.height;
// double width = MediaQuery.of(context).size.width;
return SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Sign Up",style: Theme.of(context).textTheme.headline4,),
SizedBox(height: height * 0.02,),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Profile Name',
),
controller: _nameController,
),
SizedBox(height: height * 0.02,),
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Email Address',
),
controller: _emailController,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Note: Please be unique"),
],
),
emailWarning ? Text("Email is already used", style: TextStyle(color: Colors.red),)
: Container(),
SizedBox(height: height * 0.02,),
TextField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
),
controller: _passwordController,
),
passwordNotMatch ? Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Password Not Match", style: TextStyle(color: Colors.red),),
],
) : SizedBox(height: 0,),
SizedBox(height: height * 0.02,),
TextField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Confirm Password',
),
controller: _confirmPasswordController,
),
ElevatedButton(
onPressed: () {
if (!isConnected) {
setState(() {
networkWarning = !networkWarning;
});
} else if (_confirmPasswordController.text != _passwordController.text) {
setState(() {
passwordNotMatch = !passwordNotMatch;
});
}
// else if(_confirmPasswordController.text == null || _passwordController.text == null){}
else {
setState(() {
isSignedUp = !isSignedUp;
});
}
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Register", style: TextStyle(fontSize: height * 0.025),),
),
),
],
),
),
],
),
),
);
}
Widget registerProcess(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.headline3,
textAlign: TextAlign.center,
child: FutureBuilder<UserCredential>(
future: _signUp(),
builder: (context, snapshot) {
List<Widget> children;
if (snapshot.hasData) {
children = <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.green,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('You\'re registered. Please check your email for verification'),
)
];
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
} else if (snapshot.hasError) {
print(snapshot.error.toString());
children = <Widget>[
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: Email isn\'t valid or already use'),
)
];
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
} else {
children = <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting result...'),
)
];
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
}
}
)
);
}
}
Although I registered any unique email, it still raise an exception: [firebase_auth/email-already-in-use] The email address is already in use by another account. (Output from print(snapshot.error.toString())). I've checked at my firebase console repeatedly. Are there any mistakes?
Firebase is known for storing some cached data (including authentication and firestore data) in your device once you install the app and start using it's services.
In other words, it's very likely that you're getting this exception because you've deleted the user from firebase but the cached data (which firebase always checks first) is still in your device. Make sure that you really deleted the user in firebase, completely uninstall the app from your device, run flutter clean and then try again. Let me know if it works

How can I add data from the Firebase database in a reversed order? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I am currently developing a chat app. The problem is, that when someone types something it shows randomly on the chat screen (after a user registered and logged in).
I want it to look like a normal chat app, so the last message should be at the bottom of the screen. I even reversed the data and list view, but it didn't work.
My code:
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,
});
},
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
),
],
),
),
);
}
}
class MessagesStream 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;
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,
),
),
),
),
],
),
);
}
}
You add messages with:
_firestore.collection('messages').add({
'text': messageText,
'sender': loggedInUser.email,
});
Firestore doesn't have any built-in default ordering for documents. If you want to be able to order the documents, you will need to add a value to each document to allow that.
For example, you could add a timestamp of when the document was created with:
_firestore.collection('messages').add({
'text': messageText,
'sender': loggedInUser.email,
'createdAt': FieldValue.serverTimestamp()
});
By using FieldValue.serverTimestamp() the server will automatically populate this field, which you can then use when reading the data with:
stream: _firestore.collection('messages').orderBy('createdAt', descending: true).snapshots()

Show loading indicator /spinner when the page data isn't fully loaded from Firebase - Flutter

In my Flutter app, I am using ModalProgressHUD to show a spinner when I click on save buttons in my form screens and it stops spinner once data successfully writes to Firebase.
I have this screen that uses Listview.builder to display a list of all my expenses and I want to automatically show spinner as soon as the page displays, and to stop spinner once all the data from Firebase fully loads.
I need assistance in doing this. I've pasted excerpt of my code as shown below. Thanks in advance.
//class wide declaration
bool showSpinner = true;
Widget build(BuildContext context) {
ExpenseNotifier expenseNotifier = Provider.of<ExpenseNotifier>(context);
Future<void> _resfreshList() async {
expenseNotifier.getExpenses(expenseNotifier);
var expenseList = ExpenseNotifier.getExpenses(expenseNotifier);
if (expenseList != null) {
setState(() {
showSpinner = false;
});
}
return Scaffold(
body: ModalProgressHUD(
inAsyncCall: showSpinner,
child: RefreshIndicator(
onRefresh: _resfreshList,
child: Consumer<ExpenseNotifier>(
builder: (context, expense, child) {
return expense == null
? Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
PaddingClass(bodyImage: 'images/empty.png'),
SizedBox(
height: 20.0,
),
Text(
'You don\'t have any expenses',
style: kLabelTextStyle,
),
],
)
: ListView.separated(
itemBuilder: (context, int index) {
var myExpense = expense.expenseList[index];
return Card(
elevation: 8.0,
color: Colors.white70,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
RegularExpenseTextPadding(
regText:
'${_formattedDate(myExpense.updatedAt)}',
),
Container(
margin: EdgeInsets.all(20.0),
padding: const EdgeInsets.all(15.0),
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(5.0)),
border: Border.all(
color: kThemeStyleBorderHighlightColour),
),
child: Row(
children: <Widget>[
Expanded(
flex: 5,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
'${myExpense.amount}',
style: kRegularTextStyle,
),
SizedBox(
height: 20.0,
),
Text(
myExpense.description,
style: kRegularTextStyle,
),
],
),
),
Expanded(
flex: 1,
child: GestureDetector(
onTap: () {
expenseNotifier.currentExpense =
expenseNotifier
.expenseList[index];
Navigator.of(context).push(
MaterialPageRoute(builder:
(BuildContext context) {
return ExpenseDetailsScreen();
}));
},
child: Icon(
FontAwesomeIcons.caretDown,
color: kThemeIconColour,
),
),
),
],
),
),
],
),
);
},
separatorBuilder: (BuildContext context, int index) {
return SizedBox(
height: 20.0,
);
},
itemCount: expenseNotifier.expenseList.length,
);
},
),
),
),
);
}
this is an example from my app:
bool _isLoading = false; <- default false
bool _isInit = true; <- to mae it only load once
#override
void initState() {
if (_isInit) {
// activating spinner
_isLoading = true;
// your function here <------
_isInit = false;
super.initState();
}
Initstate gets called before the user can see any kind of thin in your app, so this is the perfect place to make your firebase data load. with this logic from above the loading spinner shows as long you are receiving the data. And your body looks like the following then:
#override
Widget build(BuildContext context) {
return _isLoading <- is loading condition true? shows spinner
? Center(child: CircularProgressIndicator()) <- loading spinner
// else shows your content of the app
: SafeArea(
child: Container()
....

Want to pass the total price in the bottom navigation bar in flutter

How can i show the total amount in the bottom navigation bar... The app uses firebase backend... I have a filed in my database name total price of each item... Now I want to fetch the total price of each item and then add it and display in the bottom navigation bar..
I have attach my firebase backend screen shot.. what i need is to get all the value of the field 'total' add it up and show it in the bottom bar below the Total which is hardcoded as 999 currently section...
It would be helpful if some let me know how to do it.. I am new to app development and flutter as well
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
import 'dart:async';
import 'package:fluttertoast/fluttertoast.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final myController = TextEditingController(); ///Alert Dialog box input text myController will be use to store the number of qty
String id;
var qty;
var price;
var total;
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home:Scaffold(
bottomNavigationBar: new Container( //// Bottom Naviagtion Bar for check out and Total price
color: Colors.white,
child: Row(
children: <Widget>[
Expanded(child: ListTile(
title: Text("Total"),
subtitle: Text("Rs 999"),
),),
Expanded(
child: MaterialButton(onPressed:() {},
child: Text("Check Out",style: TextStyle(color: Colors.white),),
color: Colors.red,) ,
)
],
),
),
appBar: AppBar(title: Text('MyKart'),
),
body: (
StreamBuilder(
stream: Firestore.instance.collection('KartDetails').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot kartDetails = snapshot.data.documents[index];
return Container(
height: 150,
child: Card(
elevation: 10,
child: Container(
height: 100,
width: 100,
child: Row(
children: <Widget>[
Container(
width: 100,
height: 100,
child: Image.network(kartDetails['img']),
),
Container(
child: (Text(kartDetails['item'])),
),
Container(
width: 50,
child: (Text('Rs '+kartDetails['price'].toString(),textAlign: TextAlign.end,)),
),
Container(
margin: EdgeInsets.only(left: 20),
height: 120,
width: 50,
color: Colors.white10,
child: Column(
children: <Widget>[
RaisedButton(
color: Colors.grey,
onPressed: (){
showDialog(context: context,
builder: (BuildContext context){
return Dialog(
child: Container(
height: 250,
color: Colors.white10,
child: Container(
margin: EdgeInsets.all(40.0),
child: Column(
children: <Widget>[
TextField(
controller: myController,
keyboardType: TextInputType.number,
decoration: InputDecoration(hintText: 'Enter the Quantity'),
),
Container(
height: 50,
),
RaisedButton(
color: Colors.blue,
child: Text('Submit'),
onPressed: () async{
qty = myController.text;
//==================================================================Total Number of QTY ENTERED==========================================//
if (int.parse(qty)>0 && int.parse(qty)>=5) {
CollectionReference collectionRefernce = Firestore
.instance.collection(
'KartDetails');
QuerySnapshot querySnapshot = await collectionRefernce
.getDocuments();
querySnapshot
.documents[index]
.reference
.updateData(
{"quantity": qty});
//==================================================================Calculate price for each product==========================================//
price = kartDetails['price'];
total=int.parse(qty)*price;
querySnapshot
.documents[index]
.reference
.updateData(
{"total": total});
print(myController
.toString());
Navigator.of(context)
.pop();
myController.clear();
Fluttertoast.showToast(msg: "Quantity Updated",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 20.0
);
}
else if(int.parse(qty) < 5 || int.parse(qty)<0) {
Fluttertoast.showToast(msg: "Minimum 5 quanity",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 20.0
);
myController.clear();
}
else {
Fluttertoast.showToast(msg: "Please enter valid quantity",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 20.0
);
myController.clear();
}
//Firebase query
},
)
],
),
),
),
);
});
},
child: Icon(Icons.shopping_basket),
),
Container(
height: 20,
),
RaisedButton(
color: Colors.grey,
child: Icon(Icons.delete,color: Colors.black87,),
)
],
),
),
Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 3),
height: 50,
width: 70,
child: Center(child: Text('Quantity')),
),
Container(
width: 70,
child: Center(child: Text((kartDetails['quantity']).toString())),
),
Container(
margin: EdgeInsets.only(top: 25),
child: Center(child: Text('Total Price')),),
Container(
margin: EdgeInsets.only(left: 3),
width: 70,
child: Center(child: Text(("Rs " + (kartDetails['total']).toString()))),
),
],
),
],
),
),
),
);
},
);
}
else{
return Center(
child: Container(),
);;
}
},
)
),
),
);
}
}
You can easily do it with this method:
var totalCartValue = 0;
String getCartTotal() async {
QuerySnapshot snapshot = await Firestore.instance
.collection('KartDetails')
.getDocuments();
snapshot.documents.forEach((doc) {
setState((){
totalCartValue += doc.data['total'];
});
});
return totalCartValue.toString();
}
P.S: This method will give you the total of all values in the KartDetails collection not the total for the current user, for the current user it should be like this:
var totalCartValue = 0;
String getCartTotal() async {
QuerySnapshot snapshot = await Firestore.instance
.collection('KartDetails')
.where("UID", isEqualTo: FirebaseAuth.instance.currentUser().toString())
.getDocuments();
snapshot.documents.forEach((doc) {
setState((){
totalCartValue += doc.data['total'];
});
});
return totalCartValue.toString();
}
And use it in the UI that way:
class YourClassName extends StatefulWidget {
#override
_YourClassNameState createState() => _YourClassNameState();
}
class _YourClassNameState extends State<YourClassName> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: //your body code,
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.cart_shopping),
title: new Text(totalCartValue),
),
]
)
);
}
}
You should call it inside your initState so it gets executed whenever app starts:
#override
initState(){
super.initState();
getCartTotal();
}
This is a very vague question, so I will give a shot at one method of solving this. In order to do this you will need some sort of state management solution. I suggest using provider. Flutter has a tutorial on provider here. Essentially you will have to create a ChangeNotifier that every time would onAddItem and onDeleteItem and call notifyListeners() whenever you add or delete items. Then your bottom bar would simply use a Consumer to get the values from your ChangeNotifier. As I said this is just one approach, but since you did not give a replicable example this is the best I can do.

Resources