Related
I am having issue with the cart notification panel as it displays the product-quantity and price of the previous user and is not updated even after i add products from new user account.
This is the code for notification panel
Widget build(BuildContext context) {
final _cartProvider = Provider.of<CartProvider>(context);
_cartProvider.getCartTotal();
return Container(
height: 45,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: AppColors.buttonnavigation,
borderRadius: BorderRadius.only(
topRight: Radius.circular(18), topLeft: Radius.circular(18))),
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'${_cartProvider.cartQty}${_cartProvider.cartQty == 1 ? 'Item' : 'Items'}',
style:
TextStyle(color: Colors.white, fontSize: 14)),
Text(
' | ',
style: TextStyle(color: Colors.white),
),
Text('Rs.${_cartProvider.subTotal}',
style:
TextStyle(fontSize: 18, color: Colors.white)),
],
),
],
),
),
Here is the Provider i've used
class CartProvider with ChangeNotifier {
final CartServices _cart = CartServices();
double subTotal = 0.0;
int cartQty = 0;
QuerySnapshot? snapshot;
List cartList = [];
Future<double?> getCartTotal() async {
var cartTotal = 0.0;
List _newList = [];
QuerySnapshot snapshot =
await _cart.cart.doc(_cart.user!.uid).collection('products').get();
if (snapshot == null) {
return null;
}
snapshot.docs.forEach((doc) {
if (!_newList.contains(doc.data())) {
_newList.add(doc.data());
cartList = _newList;
notifyListeners();
}
cartTotal = cartTotal + doc['total'];
});
subTotal = cartTotal;
cartQty = snapshot.size;
this.snapshot = snapshot;
notifyListeners();
return cartTotal;
}
}
I am not exactly sure what went wrong in this as i've just started in flutter and firebase.
You might want to check what documents you are listening to. From what you say, your app may still be listening to the old user's document and thus you see no change.
P.S.: I believe you should provide a minimal piece of code to recreate the issue you are facing and using pictures is not a very good way. You should try to add the code here as text instead.
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(...
I have just completed this basic chat app based on a tutorial on YouTube. NO user name needed for using it. But my messages are not ordering correctly. It is sorting as auto generated ID on FireStore. There are total two field in Firebase "message" collection that I have created
"username" {this user name randomly generated by FireBase as user ID. example: 1Mrayhz7EKL7MklHXUxv}
"messagetext"
I don't understanding what should I do now
//Here's the code
class MyApp extends StatelessWidget {
final TextEditingController messaingTextBox = TextEditingController();
final CollectionReference cr =
FirebaseFirestore.instance.collection('messages');
#override
Widget build(BuildContext context) {
body:
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(
child: StreamBuilder(
stream: cr.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
return ListView.builder(
reverse: true,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(18.0),
child: Column(
children: [
Row(
children: [
Container(
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Text(
snapshot.data.documents[index]
.data()['username'],
),
),
),
SizedBox(
width: 10,
),
Container(
// width: 38.0,
width: MediaQuery.of(context).size.width / 1.6,
child: Padding(
padding: const EdgeInsets.all(9.0),
child: Column(
children: [
Text(
snapshot.data.documents[index]
.data()['messagetext'],
),
],
),
),
),
],
),
],
),
);
},
);
},
),
),
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: TextFormField(
cursorColor: HexColor("#003F51"),
style: TextStyle(color: Colors.black),
controller: messaingTextBox,
),
),
),
FloatingActionButton(
onPressed: () {
Message m = Message(
username: "unkown ",
messagetext: messaingTextBox.text.toString());
cr.add(m.mapMessages());
},
child: Icon(Icons.send),
),
],
),
],
);
}
}
class Message {
final String username;
final String messagetext;
Message({this.messagetext, this.username});
mapMessages() {
return {'messagetext': messagetext, 'username': username};
}
}
Please help me for solving this isue
You need to add third field to store time.
Then in the code try to add orderBy operator.
FirebaseFirestore.instance.collection('messages')
.orderBy('time_field', descending: true);
It's quite simple just add a timestamp with each message lets say createdAt, and the use
NOTE: The type of createdAt must be Date
FirebaseFirestore.instance.collection('messages').orderBy('createdAt', descending: true)
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()
....
I am currently able to display a Listview filled with data from my Firestore database. My current problem is, that I want to make it dissmissable, so I need to be able to use functions such as:
setState(() {
items.removeAt(index);
});
Now, I read up on how to generate a list, but none of the examples mention a firebase Streambuilder like I am using. So I was just wondering if it was possible to make the data into a list? And if not, if there are any other ways to make a firestore listview dissmissable? Here is how I currently get the data:
Container(
child: StreamBuilder(
stream: Firestore.instance.collection('users').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
);
} else {
return ListView.builder(
scrollDirection: Axis.vertical,
padding: EdgeInsets.all(10.0),
itemBuilder: (context, index) => buildItem(context, snapshot.data.documents[index]),
itemCount: snapshot.data.documents.length,
);
}
},
),
),
Thanks in advance, any help is appreciated.
Builditem looks like this:
Widget buildItem(BuildContext context, DocumentSnapshot document) {
if (document['id'] == currentUserId || document['gender'] == null) {
return Container();
}
if (currentUserPreference == 'male' && currentUserGender == 'male') {
return showGayMales(document);
}
And the ShowGayMales method looks like this:
Widget showGayMales(DocumentSnapshot document) {
if (document['id'] == currentUserId || document['id'] == nopeId || ) {
return Container();
} else {
return Container(
child: Slidable(
delegate: new SlidableScrollDelegate(),
actionExtentRatio: 0.3,
child: Card(
child: Padding(
padding:EdgeInsets.fromLTRB(20.0, 10.0, 25.0, 10.0),
child: Row(
children: <Widget>[
Material(
color: Colors.transparent,
child: Icon(
FontAwesomeIcons.male,
color: textColor,
),
),
new Flexible(
child: Container(
child: new Column(
children: <Widget>[
new Container(
child: Text(
'${document['aboutMe']}',
style: TextStyle(color: textColor, fontSize: 30.0),
),
alignment: Alignment.centerLeft,
margin: new EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 5.0),
),
new Container(
child: Row(
children: <Widget>[
Text(
'-'+'${document['nickname'] ?? 'Not available'}',
style: TextStyle(color: textColor, fontSize: 15.0, fontWeight: FontWeight.bold),
),
Text(
','+' ${document['age'] ?? ''}'
)
],
),
alignment: Alignment.centerLeft,
margin: new EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 0.0),
)
],
),
margin: EdgeInsets.only(left: 20.0),
),
),
],
),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
),
actions: <Widget>[
new IconSlideAction(
caption: 'Not interested!',
color: errorColor,
icon: Icons.clear,
onTap: () => notinterested('${document['id']}'),
),
],
secondaryActions: <Widget>[
new IconSlideAction(
caption: "Interested!",
color: primaryColor,
icon: Icons.check,
onTap: () => interested('${document['nickname']}', '${document['id']}', '${document['gender']}', '${document['aboutMe']}', '${document['age']}', '${document['preference']}'),
),
],
),
margin: EdgeInsets.only(bottom: 10.0, left: 5.0, right: 5.0),
);
}
}
You can fetch Firestore data and add it to a List by mapping it to an Object first.
List<Users> userList;
Future<void> getUsers() async {
userList = [];
var collection = FirebaseFirestore.instance.collection('users');
collection.get().then((value) {
value.docs.forEach((users) {
debugPrint('get Users ${users.data()}');
setState(() {
// Map users.data to your User object and add it to the List
userList.add(User(User.setUserDetails(users.data())));
});
});
});
}
// Let's say this is User object
class User {
var username;
User(User doc) {
this.username = doc.getUsername();
}
getUsername() => username;
// fetch name using Firestore field name
User.setUserDetails(Map<dynamic, dynamic> doc)
: username = doc['name'];
}