UI not updated Flutter Web - firebase

I have a Table reading data from Firestore, using StreamBuilder. I have a button Add Element below the table that when clicked opens a pop-up form. After the user fills the form and clicks the button, data is stored in Firestore, the Dialog form is closed and the user is redirected to the table. When I use only text form fields in my form, the table is updated with the new data that the user just pushed in Firestore. The problem started to occurs when I added an Upload Picture Form, which uploads the picture in Firebase Storage and pushes the download URL as a field inside the other information of the form. I fill the form and the Table doesn't update. The project is in Flutter Web so I am using image_picker_web for the upload process and the Image file is MediaInfo type. As I said it started to happen only when I added the upload picture form along with other TextFormFields.
// Upload Picture Field
InkWell(
onTap: () async {
final MediaInfo _image =
await ImagePickerWeb.getImageInfo;
setState(() {
image = _image;
});
},
child: Container(
child: Center(
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Icon(
CupertinoIcons.cloud_upload,
color: Colors.grey[600],
),
const SizedBox(
width: 5,
),
Text(
'Upload the invoice picture',
style: TextStyle(
color: Colors.grey[600],
),
),
],
),
),
),
),
//Create Invoice Button
MaterialButton(
onPressed: () async {
Invoice invoice = Invoice(
_controllerInvoiceNumber.text,
_controllerLocation.text,
invoiceDate,
_controllerAmount.text,
);
Database().addNewInvoice(
invoice,
_userId!,
image!,
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const HomePage(),
),
);
}
},
child: const Text(
'Create',
),
),
//Table
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('Invoices')
.snapshots(),
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Column(
children: [
SizedBox(
child: PlutoGrid(
columns: editableColumns,
rows: _createRows(snapshot.data),
),
),
Padding(
padding: const EdgeInsets.all(10),
child: Center(
child: MaterialButton(
child: const Text(
'Add Invoice',
style: TextStyle(color: Colors.white),
),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return const AddInvoiceForm();
},
);
},
),
),
),
],
);
},
),

Related

How to perform calculations on values retrieving from firebase

Here is the screenshot of my app screen where I am retrieving data from firebase using stream builder:
enter image description here
Now I want to add all the entries in "Cash In" and "Cash Out " and display their sums in the above cards"Cash in hand " and "Today's balance" respectively.
Do I need to create another stream builder or something else?
Here is my code:
class CaShBookRegister extends StatefulWidget {
const CaShBookRegister({Key? key}) : super(key: key);
#override
_CaShBookRegisterState createState() => _CaShBookRegisterState();
}
class _CaShBookRegisterState extends State<CaShBookRegister> {
final _firestore = FirebaseFirestore.instance;
DateTime date = DateTime.now();
late var formattedDate = DateFormat('d-MMM-yy').format(date);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 18.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(
height: 16.0,
),
Padding(
padding: const EdgeInsets.all(14.0),
child: Row(
children: [
Expanded(
child: ReuseableCard(
textcolour: Colors.white,
buttonColour: Colors.green,
text: "Cash in hand",
),
),
const SizedBox(
width: 8.0,
),
Expanded(
child: ReuseableCard(
buttonColour: Colors.red,
textcolour: Colors.white,
text: "Today's balance",
),
),
],
),
),
StreamBuilder(
stream: _firestore.collection('CASHINCASHOUT').snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
if (snapshot.hasData) {
List<DataCell> displayedDataCell = [];
for (var item in snapshot.data!.docs) {
displayedDataCell.add(
DataCell(
Text(
item['DATE'].toString(),
),
),
);
displayedDataCell.add(
DataCell(
Text(
item['CASHIN'].toString(),
style: const TextStyle(color: Colors.green),
),
),
);
displayedDataCell.add(
DataCell(
Text(
item['CASHOUT'].toString(),
style: const TextStyle(color: Colors.red),
),
),
);
}
return FittedBox(
child: DataTable(
columns: const <DataColumn>[
DataColumn(
label: Text(
'Date',
),
),
DataColumn(
label: Text(
'Cash In',
),
),
DataColumn(
label: Text(
'Cash Out',
),
),
],
rows: <DataRow>[
for (int i = 0; i < displayedDataCell.length; i += 3)
DataRow(cells: [
displayedDataCell[i],
displayedDataCell[i + 1],
displayedDataCell[i + 2]
]),
],
),
);
}
return const CircularProgressIndicator();
},
),
const Spacer(),
Padding(
padding: const EdgeInsets.all(14.0),
child: Row(
children: [
Expanded(
child: ReusableButton(
color: Colors.red,
text: "Add Enteries",
onpress: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const CashInCashOut(),
),
);
},
)),
],
),
),
],
),
),
);
}
}
one way to do this is by subscribing to your stream from firestore inside
initState where you can calculate "Cash in hand " and "Today's balance" and substitute into variables with setState.
Then you pass that variables into your widget, whenever that variable is updated, widget will rebuild itself
int _cashInHand = 0;
int _balance = 0;
#override
void initState() {
_firestore.collection('CASHINCASHOUT').snapshots().listen((qs) {
setState(() {
_cashInHand = <calculate cash in hand from snapshots>
_balance = <calculate today's balance from snapshots>
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
....
Padding(
padding: const EdgeInsets.all(14.0),
child: Row(
children: [
Expanded(
child: ReuseableCard(
textcolour: Colors.white,
buttonColour: Colors.green,
text: _cashInHand.toString(), // <- pass the variable
),
),
const SizedBox(
width: 8.0,
),
Expanded(
child: ReuseableCard(
buttonColour: Colors.red,
textcolour: Colors.white,
text: _balance.toString(), // <- pass the variable
),
),
],
),
),
You probably comes from an hard solid SQL background, because I did and had a huge difficult to understand differences from sql to firebase, so you must think differently.
What I suggest as where I viewed all Firestore YouTube videos (official Chanel) and flutter same, also I did read a lot of official documentation, I can your options are:
A. When storing an new transaction (I am assuming you store every single cacheIn/Out doc), you do calculate the new balance for that date, this way you store that balance as a solid value and just request the result as you want. I think this is the best option for you, and to do that, you can do a transaction to keep things fine like this:
// Run transaction to do all changes at once
FirebaseFirestore.instance
.runTransaction((transaction) async {
// Store you "transaction"
await FirebaseFirestore
.instance
.collection("TRANSACTIONCOLLECTION")
.add(transaction.toJson());
// With your transaction, update your CACHEINCACHEOUT report collection
transaction.update(
// Your CASHINCASHOUT doc reference
await FirebaseFirestore
.instance
.collection('CASHINCASHOUT')
.doc('itemId'),
/// You can use increment do update the value for cache in or cache out,
/// see this example as i increase in transaction.cacheIn
{
'CACHEIN': FieldValue.increment(transaction.cacheIn)
}
)
});
You can learn more about transactions at <(FlutterFire Docs)[https://firebase.flutter.dev/docs/firestore/usage#transactions]>
Also I viewed this entire playlist at least 3 times to calm down my furious SQL mind for 15 years at (Firebase YouTube)[https://www.youtube.com/watch?v=v_hR4K4auoQ&list=PLl-K7zZEsYLluG5MCVEzXAQ7ACZBCuZgZ]

Firebase "CurrentUser displayName" won't update with bottomsheet

My application has Firebase Authentication implemented and each user have to input their display name to register into the application.
Inside my application I want the user to be able to update this display name.
The logic is very simple, user clicks 'Change Display Name' button, a ModalBottomSheet appears where user can input his new display name and save it. But this way the screen doesn't gets updated when the display name is changed until I hot reload or restart the application.
class Body extends StatelessWidget {
final FirebaseAuth _auth = FirebaseAuth.instance;
#override
Widget build(BuildContext context) {
String displayName = _auth.currentUser.displayName;
final _formKey = GlobalKey<FormState>();
String newDisplayName;
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Current display name is:',
style: TextStyle(fontSize: 20),
),
Text(
'$displayName',
style: TextStyle(fontSize: 25),
),
RaisedButton(
child: Text('Change Display Name'),
onPressed: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) {
return Padding(
padding: MediaQuery.of(context).viewInsets
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
decoration: InputDecoration(
labelText: 'New Display Name',
),
keyboardType: TextInputType.text,
onSaved: (value) {
newDisplayName = value;
},
),
RaisedButton(
child: Text('Change'),
onPressed: () {
_formKey.currentState.save();
_auth.currentUser
.updateProfile(displayName: newDisplayName);
Navigator.pop(context);
},
),
],
),
),
);
},
);
},
),
],
),
),
);
}
}
I have tried to call .reload in different places but the display name won't change until page is refreshed.
_auth.currentUser.reload();
EDIT: Solution thanks to https://stackoverflow.com/users/11921453/twelve
Wrap the manin widget with the following StreamBuilder and you will be able to retrieve the snapshot.data.displayName instantly.
child: StreamBuilder<User>(
stream: FirebaseAuth.instance.userChanges(),
Firebase has a stream to listen to user changes. Define this stream next to your _auth.
Wrap your widget-tree with au streambuilder and set the value to the defined stream.
Now the widget-tree gets rebuild every time the user data changes.

How to get data from two collections in firebase using Flutter

This is my problem:
I have a ListPost StatefulWidget where I want to display a list of widgets that contains the user's account image, the user's name, and the user's posts images(similar to Facebook feeds), however, I have gotten to the point that I need to get that data from two different collections in Firebase (see my firebase collections image below).
The good thing is that I have been able to get that data only from one collection(userFeed) and display that data in my ListPost file in different widgets, however, I do not know how to get data from another collection in Firebase using the same streamBuilder and display all that data I want to display in other widgets in my ListPost screen.
So, my specific question is:
How can I make my ListPost screen to populate data from 2 different collections in Firebase using a stream builder or another type of implementation?
This is the firebase image
This is the complete code for the ListPost screen
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'models/post_model.dart';
final _stream = Firestore.instance.collection('userFeed').snapshots();
class ListPosts extends StatefulWidget {
#override
_ListPostsState createState() => _ListPostsState();
}
class _ListPostsState extends State<ListPosts> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
//this is the Streambuilder to get the data however it only lets me to get one collection
child: StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
return ListView.builder(
itemExtent: 550.0,
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int data) {
//here I get the data from the userFeed colecction
Post post = Post.fromDoc(snapshot.data.documents[data]);
return Column(
children: <Widget>[
GestureDetector(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 10.0,
),
child: Row(
children: <Widget>[
CircleAvatar(
radius: 25.0,
backgroundColor: Colors.grey,
backgroundImage: post.imageUrl.isEmpty
? AssetImage(
'assets/images/user_placeholder.jpg')
: CachedNetworkImageProvider(post.imageUrl),
),
SizedBox(width: 8.0),
Text(
post.caption,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
GestureDetector(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
height: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
image: DecorationImage(
image:
CachedNetworkImageProvider(post.imageUrl),
fit: BoxFit.cover,
),
),
),
],
),
),
],
);
},
);
},
),
),
);
}
}
UPDATE 05-22-2020 HOW I FIXED THE ISSUE
Credits to the user griffins, he helped me to fix this issue.
This is what I do:
I nested my StreamBuilder so I can use 2 streams at the same time
return StreamBuilder(
stream: _stream,
builder: (context, snapshot1) {
return StreamBuilder(
stream: _stream2,
builder: (context, snapshot2) {
if (!snapshot2.hasData) return const Text('Loading...');
if (!snapshot1.hasData) return const Text('Loading...');
return ListView.builder(
itemExtent: 550.0,
itemCount: snapshot2.data.documents.length,
itemBuilder: (BuildContext context, int data) {
User user = User.fromDoc(snapshot2.data.documents[data]);
Post post = Post.fromDoc(snapshot1.data.documents[data]);
return buildbody(user, post, context);
},
);
},
);
},
);
You can can make you body take a widget ListView and for the Listview children have both your lists.
example
body: ListView(
children: <Widget>[
---list1----
--list2-----
]);
or you can use a custom scroll view
return new Scaffold(
appBar: new AppBar(
title: new Text("Project Details"),
backgroundColor: Colors.blue[800]),
body:
new CustomScrollView(
slivers: <Widget>[
new SliverPadding(padding: const EdgeInsets.only(left: 10.0,right: 10.0,
top: 10.0,bottom: 0.0),
sliver: new SliverList(delegate:
new SliverChildListDelegate(getTopWidgets())),
),
new SliverPadding(padding: const EdgeInsets.all(10.0),
sliver: new SliverList(delegate: new SliverChildListDelegate(
getSfListTiles()
))),
new SliverPadding(padding: const EdgeInsets.all(10.0),
sliver: new SliverList(delegate: new SliverChildListDelegate(
getWorkStatementTiles()
))),
]
)
);
update
from #RĂ©mi Rousselet answer You can nest StreamBuilder
StreamBuilder(
stream: stream1,
builder: (context, snapshot1) {
return StreamBuilder(
stream: stream2,
builder: (context, snapshot2) {
// do some stuff with both streams here
},
);
},
)

I need field data to be updated in the UI when there is some update in that field from firestore

I want to update document field value in the UI of flutter whenever there is some change in field value in realtime.
I have tried using StreamBuilder but the only output I am getting is 'Instance QuerySnapshot'
StreamBuilder(
stream: db.collection('users').snapshots(),
initialData: 0,
builder:(BuildContext context, AsyncSnapshot snapshot) {
return new Text(snapshot.data.DocumentSnapshot,
style: TextStyle(
color: Colors.yellow,
fontWeight: FontWeight.bold,
fontSize: 12.0));
},
),`
Expected output is int value of reward field in document uid.
Because of this line stream: db.collection('users').snapshots(),
You are getting the collection, but you expected the document. Refer to the following:
StreamBuilder(
stream: db.collection('users').document(userId).snapshots(), // insert the userId
initialData: 0,
builder:(BuildContext context, DocumentSnapshot snapshot) { // change to DocumentSnapshot instead of AsyncSnapshot
return new Text(snapshot.data.documentID, // you can get the documentID hear
style: TextStyle(
color: Colors.yellow,
fontWeight: FontWeight.bold,
fontSize: 12.0));
},
),`
I have done one of my project with stream builder and it's working fine. I am putting some code snippet from there please check it out this may helps you.
Code of StreamBuilder
StreamBuilder<QuerySnapshot>(
stream: db.collection("students").snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text("There is no expense");
return Expanded(
child: new ListView(
children: generateStudentList(snapshot),
),
);
},
),
code of generateStudentList
generateStudentList(AsyncSnapshot<QuerySnapshot> snapshot) {
return snapshot.data.documents
.map<Widget>(
(doc) => new ListTile(
title: new Text(doc["name"]),
subtitle: new Text(
doc["age"].toString(),
),
trailing: Container(
width: 100,
child: Row(
children: <Widget>[
IconButton(
onPressed: () {
setState(() {
_studentNameController.text = doc["name"];
_studentAgeController.text = doc["age"].toString();
docIdToUpdate = doc.documentID;
isUpdate = true;
});
},
icon: Icon(
Icons.edit,
color: Colors.blue,
),
),
IconButton(
onPressed: () {
deleteStudent(doc);
},
icon: Icon(
Icons.delete,
color: Colors.red,
),
)
],
),
),
),
)
.toList();
}
You can change fields according your needs.

How to load image to the Card from data retrieved from async task in flutter?

I'm new to flutter development. I need to load images into a card depending on data loaded via async task.
I have an async task which returns Future> user data quired from the sqlite local database. With retrieved data, I build a ListView to show users using Card. But inside the card, I'm trying to show an image which will be downloaded from Firebase Storage depending on the data retrieved from the local database. But the image URL is null.
Widget build(BuildContext context) {
var allCards = DBProvider.db.getAllCards();
return FutureBuilder<List<User>>(
future: DBProvider.db.getAllCards(),
builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
User user = snapshot.data[index];
return Card(
elevation: 8.0,
margin:
new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
child: Column(
children: <Widget>[
Stack(
children: <Widget>[
Container(
child: Image(
image: CachedNetworkImageProvider(FirebaseStorage().ref().child('employer_logo').child('00001').child('google-banner.jpg').getDownloadURL().toString()),
fit: BoxFit.cover,
),
),
Positioned(
bottom: 0,
left: 0,
child: Container(
padding: EdgeInsets.all(10),
child: Text(
'Google Incorperation',
style: TextStyle(
fontSize: 20, color: Colors.white),
),
),
)
],
),
Container(
decoration: BoxDecoration(
color: Colors.white10,
),
child: ListTile(
title: Text(user.fname + " " + user.lname,
style: TextStyle(
color: Colors.blue[400], fontSize: 20)),
subtitle: Text(user.designation,
style: TextStyle(
color: Colors.blue[300], fontSize: 16)),
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Profile(
user.fname,
user.lname,
user.uid,
user.designation,
user.mobile,
user.employerId)))
},
),
)
],
),
);
},
);
}
},
);
}
I expect to show images downloaded from firebase storage
This would be my first answer, and there are probably many ways to improve my answer here. But I will give it a go: Actually, you will have to look up a lot on Futuresand Streams, because it is quite a big part in many a app. If your app needs any content on the web, it will need Futures, or it's bigger counterpart Stream. In this case, where you want to set up a Listview with probably multiple images, I would go for a Stream. Also, I would save all my database logic in a seperate file. However, if you don't want to modify your code too much now, I would use a FutureBuilder.
I've seen you already use one of them in your code. But in this case, use:
...
int maxsize = 10e6.round(); // This is needed for getData. 10e^6 is usually big enough.
return new Card (
FutureBuilder<UInt8List> ( // I also think getting Data, instead of a DownloadUrl is more practical here. It keeps the data more secure, instead of generating a DownloadUrl which is accesible for everyone who knows it.
future: FirebaseStorage().ref().child('entire/path/can/go/here')
.getData(maxsize),
builder: (BuildContext context, AsyncSnapshot<UInt8List> snapshot) {
// When this builder is called, the Future is already resolved into snapshot.data
// So snapshot.data contains the not-yet-correctly formatted Image.
return Image.memory(data, fit: BoxFit.Cover);
},
),
Widget build(BuildContext context) {
var allCards = DBProvider.db.getAllCards();
return FutureBuilder<List<User>>(
future: DBProvider.db.getAllCards(),
builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
User user = snapshot.data[index];
int maxsize = 10e6.round();
return Card(
elevation: 8.0,
margin:
new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
child: Column(
children: <Widget>[
Stack(
children: <Widget>[
Container(
child: FutureBuilder<dynamic>(
future: FirebaseStorage()
.ref()
.child('employer_logo')
.child('00001')
.child('google-banner.jpg')
.getDownloadURL(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState !=
ConnectionState.waiting) {
return Image(
image: CachedNetworkImageProvider(
snapshot.data.toString()),
fit: BoxFit.cover,
);
}
else {
return Text('Loading image....');
}
},
),
),
Positioned(
bottom: 0,
left: 0,
child: Container(
padding: EdgeInsets.all(10),
child: Text(
'Google Incorperation',
style: TextStyle(
fontSize: 20, color: Colors.white),
),
),
)
],
),
Container(
decoration: BoxDecoration(
color: Colors.white10,
),
child: ListTile(
title: Text(user.fname + " " + user.lname,
style: TextStyle(
color: Colors.blue[400], fontSize: 20)),
subtitle: Text(user.designation,
style: TextStyle(
color: Colors.blue[300], fontSize: 16)),
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Profile(
user.fname,
user.lname,
user.uid,
user.designation,
user.mobile,
user.employerId)))
},
),
)
],
),
);
},
);
}
},
);
}

Resources