Firebase "CurrentUser displayName" won't update with bottomsheet - firebase

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.

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]

UI not updated Flutter Web

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();
},
);
},
),
),
),
],
);
},
),

Flutter/Firebase - Right Track?

At 54 I'm self-learning Flutter and have come a long way. I am stuck (again) with a speed-bump concerning my implementation of Firebase. Although I am getting the very basic of displaying a line of data for each record the problem is getting to the details of each record. I believe the issue is that I am not referencing the record ID.
When looking at endless examples I also believe my code to display a ListView of records is bloated making it more difficult to grab a record and display all fields (edit screen).
I want to click the "Walsh-Test" links to view all fields and update. I can create a new edit/update screen my initial problem is opening the screen to the selected record.
Any advice would be greatly appreciated.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/rendering.dart';
class MeterReadHomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Color(0xff4367b1),
automaticallyImplyLeading: false,
leading: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Icon(
Icons.arrow_back,
color: Colors.white,
),
),
title: Text(
"WelakaOne",
style: TextStyle(
color: Colors.white,
),
),
),
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('meter_reads')
.orderBy('accountname')
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return Padding(
padding: const EdgeInsets.fromLTRB(6, 20, 0, 0),
child: Container(
child: GestureDetector(
onTap: () {},
child: ListView(
children: snapshot.data.docs.map(
(document) {
return Row(
children: [
Container(
width: 50,
child: Icon(
Icons.access_alarm,
size: 36,
color: Color(0xff4367b1),
),
),
Container(
child: Text(
document['accountname'],
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Color(0xff4367b1),
),
),
),
SizedBox(height: 44),
],
);
},
).toList(),
),
),
),
);
},
),
);
}
}
[![Flutter/Firebase/Open to Selected Record][1]][1]
I understand that you want to go to another page (Edit Screen) onTapping the alarm tile.
So, my suggestion is to use A ListTile widget instead of the row widget you are using.
You can read about ListTile's onTap property, here
Regarding your routing to a new screen/page, you'll have to make use of MaterialPageRoute to create a new route and using Navigator's push method you can push it onto your Material App.
Attaching an example code that uses Navigator to go to different pages, you need the BuildContext of your app. Here is an example of how you can get it:
import 'package:flutter/material.dart';
import 'package:rate_your_professor/screens/firstScreen.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Some App',
home: SomeApp(),
);
}
}
class SomeApp extends StatelessWidget {
Widget getListView(BuildContext context) {
var listView = ListView(
children: <Widget>[
Text(
"XXXXXXXXXXXXXXX",
textDirection: TextDirection.rtl,
textAlign: TextAlign.center,
),
ListTile(
leading: Icon(Icons.location_city),
title: Text("XXXXX ", textDirection: TextDirection.rtl),
subtitle: Text(
"XXXXXXXXXX",
textDirection: TextDirection.rtl,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => YourNewPage(),
),
);
},
),
],
);
return listView;
}
#override
Widget build(BuildContext context) {
return Scaffold(body: getListView(context));
}
}

navigator operation requested with a context that does not include a navigator statefulwidget [duplicate]

This question already has answers here:
Navigator operation requested with a context that does not include a Navigator
(18 answers)
Closed 4 years ago.
I am working on the Flutter code that follows. I have issues with the second button called "Regitrese". Every place I have looked work with statelesswidgets so I'm not sure how to fix it. I tried changing the void to put on it home: MyHomePage() and put MyHomePage to statefull instead of taking the statefull from MyApp bus it shows a mistake Missing concrete implementation of StatefulWidget.createState. I am not sure how is it supposed to go. Can you make a button work in a StatefulWidget? Is there a trick I am not seeing?
void main()=> runApp(new MyApp());
class MyApp extends StatefulWidget{
#override
State<StatefulWidget> createState(){
return new MyHomePage();
}
}
class MyHomePage extends State<MyApp>{
final TextEditingController rutController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
var _rut, _password;
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new Container(
padding: const EdgeInsets.all(50.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
controller: this.rutController,
decoration: InputDecoration(
labelText: 'Rut',
hintText: 'eg. 154683265',
suffixIcon: IconButton(
icon: Icon(Icons.clear),
onPressed: () {
rutController.clear();
}
)
),
),
TextFormField(
controller: this.passwordController,
decoration: InputDecoration(
labelText: 'ContraseƱa',
hintText: 'ContraseƱa',
suffixIcon: IconButton(
icon: Icon(Icons.clear),
onPressed: () {
passwordController.clear();
},
)
),
obscureText: true,
),
RaisedButton(
onPressed: (){
loginButton(rut: this.rutController.text, password: this.passwordController.text);
},
child: Text('Login'),
),
RaisedButton(
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder(context)=>SelectUserPage())
)
},
child: Text('Registrese'),
),
],
),
),
),
);
}
}
class SelectUserType extends StatelessWidget{
#override
Widget build(BuildContext context){
return new Container(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: (){
//Do something
},
child: Text(''),
),
RaisedButton(
onPressed: (){
//Do something
},
child: Text(''),
),
],
),
);
}
}
The thing is that you need to access a context below the MaterialApp widget that adds the Navigator to the tree. If you look at your code the context is above it.
One way to solve it is to move a part of the tree to another widget.
Or you can use a Builder around your button or around the Column as in the following code:
Builder(
builder: (context) => RaisedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => SelectUserType()));
},
child: Text('Registrese'),
),
),

How to use constructor objects in another state using flutter

I am creating this shopping app , the home page displays pictures and some data from firestore firebase as a gridView. THIS IS THE SCREEN OF THIS PAGE
Once the user click on one of the images in this page the app will show him another page contains more details about the product with Hero animation.
THIS IS THE SECOND PAGE
The second page is a stateless widget and it takes some parameters
#override
Widget build(BuildContext context) {
return GridView.builder(
........
itemBuilder: (BuildContext context, int index){
String image = documents[index].data['image'].toString();
String ti = documents[index].data['title'].toString();
String prix = documents[index].data['price'].toString();
String desc = documents[index].data['description'].toString();
String categ = documents[index].data['category'].toString();
return new Material(
elevation: 5.0,
borderRadius:
new BorderRadius.all(new Radius.circular(15.0)),
child: new InkWell(
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) =>
new FullScreenImagePage(image,ti,prix,desc,categ)));
},
child: new Hero(
tag: image,
child: new Column(
children: <Widget>[
new Padding(padding: EdgeInsets.all(4.0)),
new FadeInImage(
image: new NetworkImage(image),
fit: BoxFit.cover,
placeholder: new AssetImage("assets/ic.png"),
),
new Text(ti, style: TextStyle(fontSize: 13.0, height: 1.0),),
new Text('prix: $prix MAD', style: TextStyle(fontSize: 9.0),),
],
),
),
),
);
Then I receive the parameters in this class
class FullScreenImagePage extends StatelessWidget {
String imgPath ,title,price,desc,categ;
FullScreenImagePage(this.imgPath,this.title,this.price,this.desc,this.categ);
......
Padding(
padding: EdgeInsets.all(10.0),
child: Hero(
tag: imgPath,
child: Material(
elevation: 20.0,
shadowColor: Colors.black26,
child: Image(
image: NetworkImage(imgPath),
fit: BoxFit.cover,
),
),
),
),
The problem that I have is that I wanna change the stateless widget of FullScreenImagePage to stateful with keeping the same variables
if you are using IntelliJ
you can change you stateless widget into a stateful one using alt+enter
go to the class name{{FullScreenImagePage}
press alt + Enter
Just convert the stateless widget to stateful widget either manually or by using the shortcut(quick fix). Then access your variable in the state subclass using widget.variable_name

Resources