dart/firebase: issue with state widget? [duplicate] - flutter

Error: Runner[463:34314] flutter: LateInitializationError: Field 'name' has not been initialized.
I recently updated to using firebase core and nullsafety and made some changes to my code that I don't quite understand, I'm new to programming here. Any way, I've tried reading this similar thread here about it but I still don't quite grasp it. I understand that I am not using name properly in the initstate most likely but that is as much as I understand. Can someone please provide an example code of what's needed to solve for the error below?
2021-04-10 17:59:41.331476-0700 Runner[463:34314] flutter: LateInitializationError: Field 'name' has not been initialized.
class MyService extends StatefulWidget {
#override
_MyServiceState createState() => _MyServiceState();
}
class _MyServiceState extends State<MyService> {
late String name, email;
Widget currentWidget = BackgroundBetcher();
#override
void initState() {
// TODO: implement initState
super.initState();
findNameAnEmail();
}
Future<Null> findNameAnEmail() async {
await Firebase.initializeApp().then((value) async {
FirebaseAuth.instance.authStateChanges().listen((event) {
setState(() {
name = event!.displayName!;
email = event.email!;
});
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: MyStyle().primaryColor,
),
drawer: buildDrawer(),
body: currentWidget,
);
}
Drawer buildDrawer() {
return Drawer(
child: Stack(
children: [
Column(
children: [
buildUserAccountsDrawerHeader(),
buildListTileShowCartoonList(),
buildListTileInformation(),
],
),
buildSignOut(),
],
),
);
}
ListTile buildListTileShowCartoonList() {
return ListTile(
leading: Icon(
Icons.face,
size: 36,
),
title: Text('Manual Location Update'),
subtitle: Text('Send a single location update'),
onTap: () {
setState(() {
currentWidget = PageWidget();
});
Navigator.pop(context);
},
);
}
ListTile buildListTileInformation() {
return ListTile(
leading: Icon(
Icons.perm_device_info,
size: 36,
),
title: Text('Background Location Fetch Log'),
subtitle: Text('History of recorded locations'),
onTap: () {
setState(() {
currentWidget = BackgroundBetcher();
});
Navigator.pop(context);
},
);
}
UserAccountsDrawerHeader buildUserAccountsDrawerHeader() {
return UserAccountsDrawerHeader(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/wall.jpg'), fit: BoxFit.cover),
),
accountName: MyStyle().titleH3(name),
accountEmail: MyStyle().titleH3(email),
currentAccountPicture: Image.asset('images/logo.png'),
);
}
Column buildSignOut() {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ListTile(
onTap: () async {
await Firebase.initializeApp().then((value) async {
await FirebaseAuth.instance.signOut().then((value) =>
Navigator.pushNamedAndRemoveUntil(
context, '/authen', (route) => false));
});
},
tileColor: MyStyle().darkColor,
leading: Icon(
Icons.exit_to_app,
color: Colors.white,
size: 36,
),
title: MyStyle().titleH2White('Sign Out'),
subtitle: MyStyle().titleH3White('Sign Out & Go to Authen'),
),
],
);
}
}

You should be checking for nullable for the variable name. So, Use:
String? name;
instead of:
late String name
Late means,
I promise I will initialize this variable later
but this promise doesn't mean a thing when using that variable inside the build method.

findNameAnEmail is an asynchronous method, so, while it's called in initState, Flutter does not wait for it to complete before build is called. This results in your late fields being accessed before they are set.
In order to wait for a Future to complete before building, consider using FutureBuilder.

I had the same problem of lateInitializationError I find out to use ? to avoid it. if some one is facing this problem instead of late try ?.
For Example:
Gender? selectedGender;

For LateInitializationError, Simply use int? count; instead of late int count;

As i was facing the same problem i used the Future Builder the snapshot.data cant be null to handle this just use if block (snapshot.data==null) then return container with child cneter Text('Loading') until future completes screen will show loading and then will display dataSolution

I'm very new to flutter, but this worked for me.
#override
void initState() {
// TODO: implement initState
super.initState();
findNameAndEmail().whenComplete(() {
setState(() {});
});
}

Related

LateInitializationError: Field 'userModels' has not been initialized

So I am trying to get list of data that is provided by user as an input and display them by creating list of card but when I run the project I come up with an error LateInitializationError: Field 'userModels' has not been initialized. Not sure what is wrong with the code or I missed anything on the code.
class UserPage extends StatefulWidget {
#override
_UserPageState createState() => _UserPageState();
}
class _UserPageState extends State<UserPage> {
late List<UserModels>? userModels;
bool isLoading = false;
#override
void initState() {
super.initState();
refreshUserPage();
}
#override
void dispose() {
RegDatabase.instance.close();
super.dispose();
}
Future refreshUserPage() async {
setState(() => isLoading = true);
**//this.userModels = await RegDatabase.instance.readAllNotes();**
setState(() => isLoading = false);
}
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(
'Notes',
style: TextStyle(fontSize: 24),
),
actions: [Icon(Icons.search), SizedBox(width: 12)],
),
body: Center(
child: isLoading
? CircularProgressIndicator()
: userModels!.isEmpty
? Text(
'No Notes',
style: TextStyle(color: Colors.white, fontSize: 24),
)
: buildNotes(userModels),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.black,
child: Icon(Icons.add),
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(builder: (context) => AddEditUserPage()),
);
refreshUserPage();
},
),
);
Widget buildNotes(userModels) => Container(
child: ListView.builder(
itemCount: userModels.length,
padding: const EdgeInsets.only(top: 10.0),
itemBuilder: (context, index) {
return BabyCard(userModels[index]);
})
);
}
From your codes I understand that userModel can also be null but you are checking if its empty. you can also add if its null
body: Center(
child: isLoading
? CircularProgressIndicator()
: userModels == null || userModels!.isEmpty
? Text(
'No Notes',
style: TextStyle(color: Colors.white, fontSize: 24),
)
: buildNotes(userModels),
),
And don't mark userModels as late since you are adding ? it means it can be null too.
List<UserModels>? userModels;
You're making initState forced to be executed to completion with Futures left uncompleted. initState must be synchronous (cannot be async) and should not call any Future-producing routine (directly or indirectly) except to store that Future into a variable for later review.
Just change this line :
late List<UserModels>? userModels;
To this one :
List<UserModels> userModels = [];

Change card color based on alertdialog option

I have a list of cards and each card has a long press function which when clicked, pops up an alert dialog. I would like the card to change color based on the option chosen in the alert dialog. My alert dialog has 3 options:
Completed (Card should change to color green),
In Progress ( Color orange),
Cancel (Color grey).
At first, when the screen loads, it should show list of cards each painted the color based on the value saved in the database. Then, when the user long presses a card and chooses an option from the alert dialog, the card's color should change based on the chosen option. Only that particular card's color should change.
I have read somewhere that this might be achievable using valuechangenotifier. So here's what I did so far:
First I created my changenotifier class like below:
import 'package:flutter/material.dart';
class ColorChanger with ChangeNotifier{
Color _color = Colors.white;
ColorChanger(this._color);
getColor() => _color;
setTheme (Color color) {
_color = color;
notifyListeners();
}
}
Then I used it in my dart class. However, the color does not seem to change. What am I missing here?
class OrderItem extends StatefulWidget {
final ord.OrderItem order;
OrderItem(this.order);
#override
_OrderItemState createState() => _OrderItemState();
}
class _OrderItemState extends State<OrderItem> {
var _expanded = false;
var mycolor = Colors.white;
#override
Widget build(BuildContext context) {
ColorChanger _color = Provider.of<ColorChanger>(context);
var listProducts = widget.order.products;
return Card(
color: widget.order.orderStatus=='completed'
?Colors.lightGreen:widget.order.orderStatus=='inprogress'?
Colors.orangeAccent:
widget.order.orderStatus=='cancelled'?Colors.grey:mycolor,
margin: EdgeInsets.all(10),
child: Column(
children: <Widget>[
ListTile(
title: RichText(
text: new TextSpan(
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: 'Order Number : ',
style: new TextStyle(fontWeight: FontWeight.bold)),
new TextSpan(text: widget.order.uniqueOrderNumber),
],
),
),
trailing: IconButton(
icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
onPressed: () {
setState(() {
_expanded = !_expanded;
});
},
),
onLongPress: toggleSelection,
),
],
),
);
}
void toggleSelection() {
ColorChanger _color = Provider.of<ColorChanger>(context,listen:false);
Widget completeOrder = FlatButton(
child: Text('Completed'),
onPressed: () async {
try {
Navigator.of(context).pop(true);
// setState(() {
_color.setTheme(Colors.lightGreen);
// });
await Provider.of<Orders>(context, listen: false)
.updateOrder(widget.order,'completed');
} catch (error) {
}
});
Widget startOrder = FlatButton(
child: Text('In progress'),
onPressed: () async {
try {
Navigator.of(context).pop(true);
// setState(() {
_color.setTheme(Colors.orangeAccent);
//});
//Update Db to mark order in progress
await Provider.of<Orders>(context, listen: false)
.updateOrder(widget.order,'inprogress');
} catch (error) {
}
});
Widget cancelOrder = FlatButton(
child: Text('Cancel'),
onPressed: () async {
try {
Navigator.of(context).pop(false);
// setState(() {
_color.setTheme(Colors.grey);
// });
//Update Db to mark order as cancelled
await Provider.of<Orders>(context, listen: false)
.updateOrder(widget.order,'cancelled');
} catch (error) {
}
});
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text('Take Action'),
content: Text('What do you want to do with the order?'),
actions: <Widget>[
startOrder,
completeOrder,
cancelOrder
],
),
);
});
}
}
SECOND TRY based on Loren's answer.
import 'package:flutter/material.dart';
class ColorChanger with ChangeNotifier{
Color color = Colors.white;
setTheme (Color newColor) {
color = newColor;
notifyListeners();
}
}
class OrderItem extends StatefulWidget {
final ord.OrderItem order;
OrderItem(this.order);
#override
_OrderItemState createState() => _OrderItemState();
}
class _OrderItemState extends State<OrderItem> {
var _expanded = false;
//Set the color based on what was last saved in the DB
void didChangeDependencies() async {
var colorChanger = Provider.of<ColorChanger>(context, listen: false);
if(widget.order.orderStatus=='completed')
colorChanger.setTheme(Colors.lightGreen);
else if(widget.order.orderStatus=='inprogress')
colorChanger.setTheme(Colors.orangeAccent);
else if(widget.order.orderStatus=='cancelled')
colorChanger.setTheme(Colors.grey);
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
var listProducts = widget.order.products;
return Consumer<ColorChanger>(
builder: (context, colorChanger, child) {
return Card(
color: widget.order.orderStatus=='completed'
?Colors.lightGreen:widget.order.orderStatus=='inprogress'?
Colors.orangeAccent:
widget.order.orderStatus=='cancelled'?Colors.grey:mycolor,
margin: EdgeInsets.all(10),
child: Column(
children: <Widget>[
ListTile(
title: RichText(
text: new TextSpan(
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: 'Order Number : ',
style: new TextStyle(fontWeight: FontWeight.bold)),
new TextSpan(text: widget.order.uniqueOrderNumber),
],
),
),
trailing: IconButton(
icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
onPressed: () {
setState(() {
_expanded = !_expanded;
});
},
),
onLongPress: toggleSelection,
),
],
),
)};
}
void toggleSelection() {
ColorChanger _color = Provider.of<ColorChanger>(context,listen:false);
Widget completeOrder = FlatButton(
child: Text('Completed'),
onPressed: () async {
try {
Navigator.of(context).pop(true);
// setState(() {
_color.setTheme(Colors.lightGreen);
// });
await Provider.of<Orders>(context, listen: false)
.updateOrder(widget.order,'completed');
} catch (error) {
}
});
Widget startOrder = FlatButton(
child: Text('In progress'),
onPressed: () async {
try {
Navigator.of(context).pop(true);
// setState(() {
_color.setTheme(Colors.orangeAccent);
//});
//Update Db to mark order in progress
await Provider.of<Orders>(context, listen: false)
.updateOrder(widget.order,'inprogress');
} catch (error) {
}
});
Widget cancelOrder = FlatButton(
child: Text('Cancel'),
onPressed: () async {
try {
Navigator.of(context).pop(false);
// setState(() {
_color.setTheme(Colors.grey);
// });
//Update Db to mark order as cancelled
await Provider.of<Orders>(context, listen: false)
.updateOrder(widget.order,'cancelled');
} catch (error) {
}
});
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text('Take Action'),
content: Text('What do you want to do with the order?'),
actions: <Widget>[
startOrder,
completeOrder,
cancelOrder
],
),
);
});
}
}
When I do it this way, it changes the color of all the cards instead of just that one card. What am I doing wrong here?
Sharing order.dart
class OrderItem {
final String id;
final double amount;
final int deliveryFee;
final List<CartItem> products;
final DateTime dateTime;
final String deliveryMethod;
final String uniqueOrderNumber;
final String orderStatus;
final String userId;
final String customMessage;
final String customerName;
final String phoneNumber;
OrderItem(
{#required this.id,
#required this.amount,
#required this.products,
#required this.dateTime,
#required this.deliveryMethod,
#required this.uniqueOrderNumber,
#required this.isOrderComplete,
this.orderStatus,
#required this.customMessage,
#required this.deliveryFee,
this.customerName,
this.phoneNumber,
#required this.userId});
}
class Orders with ChangeNotifier {
final String authToken;
final String userId;
Orders(this.authToken, this.userId);
List<OrderItem> _orders = [];
List<OrderItem> get orders {
return [..._orders];
}
Future<void> updateOrder(OrderItem order,String orderStatus) async {
final id = order.id;
final customerId = order.userId;
final url =
'https://cv.firebaseio.com/orders/$customerId/$id.json?auth=$authToken';
try {
await http.patch(url,
body: json.encode({
'orderStatus':orderStatus
}));
} catch (error) {
print(error);
}
notifyListeners();
}
UPDATED ANSWER:
So when trying to do this with Provider I kept getting errors that would have required me to keep bugging you for more and more code to try and replicate everything you have going on, and I didn't want to get into that.
So this solution may or may not be acceptable to you because it uses GetX State Management, but it works. In addition it doesn't require wrapping your whole app in provider widgets so dealing with scope etc...is a non issue.
Let's add a statusColor property to your OrderItem model. This is what will get changed.
Color statusColor = Colors.white; // or whatever you you want the default color to be
Your updated Orders class that uses GetX instead of ChangeNotifier (again, not because Provider can't do this, but because I was dealing with too many errors and frankly GetX is easier in my opinion anyway)
class Orders extends GetxController {
final String authToken;
final String userId;
Orders(this.authToken, this.userId);
List<OrderItem> orders = []; // back to what I said earlier about no point in getters and setters here
// temp function just to test this on my end
void addOrder(OrderItem order) {
orders.add(order);
update();
}
// this loops through the list to find the matching order number,
// then updates the color for just that order
void updateOrderStatusColor({OrderItem updatedOrder, String status}) {
for (final order in orders) {
if (order.uniqueOrderNumber == updatedOrder.uniqueOrderNumber) {
switch (status) {
case 'completed':
{
order.statusColor = Colors.greenAccent;
}
break;
case 'inprogress':
{
order.statusColor = Colors.orangeAccent;
}
break;
case 'cancelled':
{
order.statusColor = Colors.grey;
}
break;
}
}
}
update(); // equivelent of notifyListeners();
}
// ...the rest of your class
}
A few small changes to your card. didChangeDependencies can go away entirely.
// it seems like you had 2 classes with the same name, which is not recommended
class OrderItemCard extends StatefulWidget {
final OrderItem order;
OrderItemCard(this.order);
#override
_OrderItemCardState createState() => _OrderItemCardState();
}
class _OrderItemCardState extends State<OrderItemCard> {
var _expanded = false;
final controller = Get.find<Orders>(); // equivilent of Provider.of... finds the same instance without needing context
void toggleSelection() {
Widget completeOrder = TextButton(
child: Text('Completed'),
onPressed: () async {
try {
Navigator.of(context).pop(true);
controller.updateOrderStatusColor(
updatedOrder: widget.order, status: 'completed'); // calling new function here
} catch (error) {}
});
Widget startOrder = FlatButton(
child: Text('In progress'),
onPressed: () async {
try {
Navigator.of(context).pop(true);
controller.updateOrderStatusColor(
updatedOrder: widget.order, status: 'inprogress');
} catch (error) {}
});
Widget cancelOrder = FlatButton(
child: Text('Cancel'),
onPressed: () async {
controller.updateOrderStatusColor(
updatedOrder: widget.order, status: 'cancelled');
try {
Navigator.of(context).pop(false);
} catch (error) {}
});
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text('Take Action'),
content: Text('What do you want to do with the order?'),
actions: <Widget>[startOrder, completeOrder, cancelOrder],
),
);
}
#override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(10),
color: widget.order.statusColor, // new color property added to your model
child: Column(
children: <Widget>[
ListTile(
title: RichText(
text: new TextSpan(
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: 'Order Number : ${widget.order.uniqueOrderNumber} ',
style: new TextStyle(fontWeight: FontWeight.bold)),
],
),
),
trailing: IconButton(
icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
onPressed: () {
setState(() {
_expanded = !_expanded;
});
},
),
onLongPress: toggleSelection,
),
],
),
);
}
}
Not sure what you have going on in your UI but here's a quick demo of how it would work in GetX. It's a simple ListView.builder populated from the orders list from the GetX Class. The GetBuilder<Orders> widget rebuilds when update() is called. Also a simple button that adds a dummy item for demo purposes. I don't know how you're generating your unique order # but I'm just using the list index for this. Both inside a column within a scaffold on a demo page.
// Equivilent of Consumer but doesn't need context nor any provider widget above it
GetBuilder<Orders>(
builder: (controller) => Expanded(
child: ListView.builder(
itemCount: controller.orders.length,
itemBuilder: (context, index) =>
OrderItemCard(controller.orders[index])),
),
),
TextButton(
onPressed: () {
final controller = Get.find<Orders>();
final orderItem = OrderItem(
orderStatus: ' ',
uniqueOrderNumber: controller.orders.length
.toString(), // just a hack to generate a unique order # for demo
);
controller.addOrder(orderItem);
},
child: Text('Add Item'),
)
Last thing is just initializing the GetX Controller. It can be done anywhere as long as its before you try and use it.
void main() {
// initialing the GetX GetxController
// not sure how you're generating the required auth and user id
// but I'm just passing in empty strings for now
Get.put(Orders('', ''));
runApp(MyApp());
}
So if you're open to GetX here, you can leave Provider for any other ChangeNotifier classes you may have in place if you want. For this you would just need to replace any Consumer<Orders> with GetBuilder<Order> and then get rid of the Provider<Orders>(create:... widget entirely.
OLD ANSWER:
You're missing a couple things in order to be using Provider properly and get the color changing the way you want.
For starters, your Card needs to be wrapped in a Consumer widget that gets notified of changes and rebuilds its children. Inside the Consumer, you need to be using the color property of the ChangeNotifier class. It doesn't need to know or care about the orderStatus because you're already explicitly telling it to change color when you call the setTheme method.
Consumer<ColorChanger>( // this is what rebuilds and changes the color
builder: (context, colorChanger, child) {
return Card(
color: colorChanger.color, // colorChanger here is equivalent of declaring final colorChanger = Provider.of<ColorChanger>(context...
child: Column(
children: <Widget>[
ListTile(
title: RichText(
text: new TextSpan(
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: 'Order Number : ',
style: new TextStyle(fontWeight: FontWeight.bold)),
new TextSpan(text: widget.order.uniqueOrderNumber),
],
),
),
trailing: IconButton(
icon: Icon(_expanded ? Icons.expand_less : Icons.expand_more),
onPressed: () {
setState(() {
_expanded = !_expanded;
});
},
),
onLongPress: toggleSelection,
),
],
),
);
});
Next, see this link as to why you're not gaining anything with using the private _color and public getColor in your ChangeNotifier class.
So lets simplify that a bit.
class ColorChanger with ChangeNotifier {
Color color = Colors.white;
ColorChanger(this.color);
setTheme(Color newColor) {
color = newColor;
notifyListeners();
}
}
Now, whenever you call the setTheme function from your dialog, that card will change to whatever color you pass into it because the Consumer widget is notified, and will rebuild with the updated color value of the ChangeNotifier class.
Something like this would be the simplest way to the thing you want to achieve:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// define a list of colors:
final colors = <Color>[
Colors.white, // this is the inital color
Colors.green,
Colors.orange,
Colors.grey
];
int index = 0;
Future<int> showMyDialog(BuildContext context) async {
// Since all Navigator.push(...) and showDialog(...) calls are futures
// we can send values alongside them when we pop the context:
// final value = await Navigator.push(...);
// or
// final value = await showDialog(...);
// then we do a:
// Navigator.pop(context, SOME_VALUE,);
// the value variable will be assigned to the one we sent
return await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Take Action'),
content: Text('What do you want to do with the order?'),
actions: <Widget>[
TextButton(
child: Text('Completed',
style: TextStyle(
color: Colors.green,
)),
onPressed: () => Navigator.pop(context, 1)),
TextButton(
child: Text('In progress',
style: TextStyle(
color: Colors.orange,
)),
onPressed: () => Navigator.pop(context, 2)),
TextButton(
child: Text('Cancel',
style: TextStyle(
color: Colors.grey,
)),
onPressed: () => Navigator.pop(context, 3)),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: <Widget>[
Card(
color: colors[index],
child: Container(width: 50, height: 50),
),
ElevatedButton(
child: Text('Show dialog'),
onPressed: () async {
// call the showMyDialog function, it returns
// a future int so we have to await it
final int _index = await showMyDialog(context);
// if the returned value (_index) is null we use
// the old one value to avoid erros in the code
setState(() => index = _index ?? index);
}),
]),
);
}
}
A very simple workaround would be to declare a global color variable cardColor and assign it to the color property of the card. Then on the alertdialog, change the 'onChange'or 'onTap' property of the widget so that on tapping, the widget changes the value of the global variable cardColor to a different color. Don't forget to do the final step i.e. changing the value of the variable, inside setState()
The best way to achieve it by using AwesomeDialog
https://pub.dev/packages/awesome_dialog
AwesomeDialog(
context: context,
dialogType: DialogType.INFO,
animType: AnimType.BOTTOMSLIDE,
title: 'Dialog Title',
desc: 'Dialog description here.............',
btnCancelOnPress: () {},
btnOkOnPress: () {},
)..show();

How to save User rating in flutter rating bar?

Im trying to saving user rating to displaying it when user comes back to page. But im a lit struggling cannot figure out how to do this. Rating works but as I said the saving not .
So what happens is that its always empty. What I actually want is that if user comes back to the page he see his rating and if he rate again and the rating is different the last rating I let him rating and if not then not and if he press clear the rating will be deleting what also works fine.
Maybe anyone can help.
lass Ratingpage extends StatefulWidget {
final int maximumRating;
final Function(int) onRatingSelected;
Ratingpage(this.onRatingSelected, [this.maximumRating = 5]);
#override
_RatingpageState createState() => _RatingpageState();
}
class _RatingpageState extends State<Ratingpage> {
int haveusercurrentchoice;
int _currentRating = 0;
Widget _buildRatingStar(int index) {
if (index < _currentRating) {
return Icon(
Icons.star,
color: Colors.yellow,
);
} else {
return Icon(
Icons.star,
color: Colors.white,
);
}
}
Widget _buildBody() {
final stars = List<Widget>.generate(this.widget.maximumRating, (index) {
return Expanded(
child: GestureDetector(
child: _buildRatingStar(index),
onTap: () {
setState(() {
_currentRating = index;
});
this.widget.onRatingSelected(_currentRating);
},
),
);
});
return Row(
children: [
Expanded(
child: Row(
children: stars,
),
),
Expanded(
child: TextButton(
onPressed: () {
setState(() {
_currentRating = 0;
});
this.widget.onRatingSelected(_currentRating);
},
child: Text(
"Clear",
style: TextStyle(color: Colors.white),
),
),
),
],
);
}
#override
Widget build(BuildContext context) {
return _buildBody();
}
if you need more information please leave a comment.
This is how im calling the page
Container(
width: 210,
height: 94,
//color: Colors.blue.withOpacity(0.5),
child: Column(
children: [
InkWell(
onTap: () {
setState(() {
israting = true;
});
// if( _rating !=null && _rating >0){
// likevideo(videos.data()['id']);}
},
child: israting
? Container(
height: 50,
margin: EdgeInsets.fromLTRB(
0, 0, 5, 0),
child: Column(
children: [
Ratingpage((rating) {
setState(() {
_rating = rating;
});
if (_rating != null &&
_rating > 0) {
likevideo(
videos.data()['id'],
_rating);
print(delteuserchoicing);
} else if (_rating ==
null ||
_rating == 0) {
dislike(
videos.data()['id'],
_rating);
}
}),
],
),
)
: Icon(
Icons.star,
size: 37,
color: videos
.data()['likes']
.contains(uid)
? Colors.yellow
: Colors.white,
),
),
it is inside a column actually
So you have an issue of storing state between pages, then you have an issue of storing the rating upon app restart. 2 separate things. You may only be concerned with the former but here's how you would do both with GetX State management and GetStorage for local database storage. Same thing can be accomplished with literally any other state management solution ie. Provider, Riverpod, Bloc etc...
GetStorage is interchangeable with SharedPreferences but I think anyone who has used both would agree GetStorage is a bit easier to use.
To clean up my example I got rid of anything that wasn't necessary for accomplishing what you're asking. Depending on whats going on in the rest of your app, you probably won't need to bring back most or all of the variables I got rid of.
For starters, let's move the logic and variables to a GetX class so they're accessible from anywhere in the app. It also helps clean up your UI code.
class RatingController extends GetxController {
int currentRating = 0;
final box = GetStorage();
#override
void onInit() { // called whenever we initialize the controller
super.onInit();
currentRating = box.read('rating') ?? 0; // initializing current rating from storage or 0 if storage is null
}
void updateAndStoreRating(int rating) {
currentRating = rating;
box.write('rating', rating); // stores to local database
update(); // triggers a rebuild of the GetBuilder Widget
}
Widget buildRatingStar(int index) {
if (index < currentRating) {
return Icon(
Icons.star,
color: Colors.yellow,
);
} else {
return Icon(
Icons.star,
color: Colors.white,
);
}
}
}
I added a button on this page just for demo purposes. Since this demo includes routing, I'm using Getx for a way easier to do routing also, but it's not at all related or necessary to answer your question. This page can now also be stateless.
class Ratingpage extends StatelessWidget {
static const id = 'rating_page'; // see GetMaterialApp for this usage
final controller = Get.find<RatingController>(); // finding the same instance of initialized controller
Widget _buildBody() {
final stars = List<Widget>.generate(5, (index) {
return GetBuilder<RatingController>( // rebuilds when update() is called from GetX class
builder: (controller) => Expanded(
child: GestureDetector(
child: controller.buildRatingStar(index),
onTap: () {
controller.updateAndStoreRating(index + 1); // +1 because index starts at 0 otherwise the star rating is offset by one
},
),
),
);
});
return Row(
children: [
Expanded(
child: Row(
children: stars,
),
),
Expanded(
child: TextButton(
onPressed: () {
controller.updateAndStoreRating(0);
},
child: Text(
"Clear",
style: TextStyle(color: Colors.white),
),
),
),
],
);
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildBody(),
ElevatedButton(
onPressed: () {
Get.to(() => OtherPage()); // equivalent of Navigator.push....
},
child: Text('Other Page'),
)
],
);
}
}
Your main method now looks like this because we need to initialize the controller and storage.
void main() async {
await GetStorage.init();
Get.put(RatingController());
runApp(MyApp());
}
And again, only necessary for easier routing, we use GetMaterialApp and define pages there.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Material App',
home: Ratingpage(),
getPages: [ // only necessary for routing, not for storage or state management
GetPage(name: OtherPage.id, page: () => OtherPage()),
GetPage(name: Ratingpage.id, page: () => Ratingpage()),
],
);
}
}
EDIT: Added with SharedPreferences due to an unmaintained package conflicting with GetStorage path provider dependency.
Add SharedPreferences prefs; to your GetX class.
This is your update function now.
void updateAndStoreRating(int rating) {
currentRating = rating;
prefs.setInt('rating', rating); //SharedPreferences way
update(); // triggers a rebuild of the GetBuilder Widget
}
Add an init function in GetX Controller class.
Future<void> initSp() async {
prefs = await SharedPreferences.getInstance();
currentRating = prefs.getInt('rating') ?? 0;
}
Now your main is a bit different.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final controller = Get.put(RatingController());
await controller.initSp();
runApp(MyApp());
}

ArgumentError (Invalid argument: Instance of 'TextEditingController')

Please help.. i'm trying to make update data page, but this error come out in this line..
Firestore.instance.collection('reg').add({'name':controllerName})
here is the code:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class EditList extends StatefulWidget {
#override
_EditListState createState() => _EditListState();
}
class _EditListState extends State<EditList> {
TextEditingController controllerName;
#override
void initState() {
controllerName = new TextEditingController();
super.initState();
}
var name;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Registration'),
backgroundColor: Colors.blue,
),
body: Container(
child: SingleChildScrollView(
padding: const EdgeInsets.all(30.0),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 20.0),
),
Text('GROUP'),
TextField(
controller: controllerName,
onChanged: (String str) {
setState(() {
name= str;
});
},
decoration: InputDecoration(
labelText: 'Name',
)),
//paste here
const SizedBox(height: 30),
RaisedButton(
onPressed: () {
if (controllerName.text.isNotEmpty) {
Firestore.instance.collection('reg').add({'name':controllerName})
.then((result){
Navigator.pop(context);
controllerName.clear();
}).catchError((err) =>print(err));
}
},
child: const Text('Submit', style: TextStyle(fontSize: 20)),
),
],
),
),
),
);
}
}
This line:
Firestore.instance.collection('reg').add({'name':controllerName})
should be replaced with:
Firestore.instance.collection('reg').add({'name':controllerName.text})
Also, you should probably give your TextField an initial value of an empty string so that it can't be null.
controllerName is not a String,
controllerName.text
use that
How do I add uuid inside the document? Tried adding .docs(uuid) before .add({ it's having an error.
CollectionReference users = FirebaseFirestore.instance.collection('users');
String? uuid = " ";
Future<void> addUser() {
FirebaseAuth.instance.authStateChanges().listen((User? user) {
if (user == null) {
print('User is currently signed out!');
} else {
uuid = user.uid;
print(uuid);
}
});
// Call the user's CollectionReference to add a new user
return users
.add({
'uuid': uuid, // John Doe
'first': firstNameController.text, // John Doe
'middle': middleNameController.text, // Stokes and Sons
'surname': surNameController.text // 42
})
.then((value) => print("User Added"))
.catchError((error) => print("Failed to add user: $error"));
}
Passing TextEditingController will definetly cause error because it just have instance of controller but you need text data to pass to function in upper most line. Controller attached to a textfield contains many of the property along with text inside the textfield.
You need to get the text from controller and pass it to the firebase function.
The line causing error:
Firestore.instance.collection('reg').add({'name':controllerName})
should be like this,
Firestore.instance.collection('reg').add({'name':controllerName.text})
and will work for sure.
Change this line
Firestore.instance.collection('reg').add({'name':controllerName})
To this line
Firestore.instance.collection('reg').add({'name':controllerName.text})
The difference is controllerName.text

Flutter testWidgets with flutter_bloc - tests fail only when executed together

I'm having a problem with the attached widget tests in flutter. When I run the tests individually, each of them succeeds; however, when I run the entire main() method, the first three tests succeed but the last two fail with the following exception:
Expected: exactly one matching node in the widget tree
Actual: ?:<zero widgets with type "SuccessDialog" (ignoring offstage widgets)>
I understand that the exception means that the widget I'm expecting is not present - what I don't understand is why the test succeeds when run individually but fails after being run after other tests. Is there some instance I need to be "resetting" after each test?
I've tried inserting "final SemanticsHandle handle = tester.ensureSemantics();" at the start of each tests and "handle.dispose();" at the end of each test but got the same results.
EDIT:
After some further investigating it seems like the problem may be with how I manage bloc instances using the flutter_bloc package. I have altered my tests to create a new testWidget instance for each test but am still encountering the same problem. Is there anything I may be missing that would cause a bloc instance to persist across testWidget objects?
My new test code looks like this:
main() {
MvnoMockClient.init();
testWidgets(
'Voucher Redemption: Tapping redeem when no values were entered yields 2 field errors',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
await tester.tap(find.byType(PrimaryCardButton));
await tester.pump();
expect(find.text("Field is required"), findsNWidgets(2));
});
testWidgets(
'Voucher Redemption: Tapping redeem when only voucher number was entered yields one field error',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
await tester.enterText(find.byType(PlainTextField), "0000000000");
await tester.tap(find.byType(PrimaryCardButton));
await tester.pump();
expect(find.text("Field is required"), findsOneWidget);
});
testWidgets(
'Voucher Redemption: Tapping redeem when only mobile number was entered yields one field error',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
await tester.enterText(find.byType(MsisdnField), "0815029249");
await tester.tap(find.byType(PrimaryCardButton));
await tester.pump();
expect(find.text("Field is required"), findsOneWidget);
});
testWidgets(
'Voucher Redemption: A successful server response yields a success dialog',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
await tester.enterText(find.byType(PlainTextField), "0000000000");
await tester.enterText(find.byType(MsisdnField), "0815029249");
await tester.tap(find.text("REDEEM"));
await tester.pump();
expect(find.byType(SuccessDialog), findsOneWidget);
});
testWidgets(
'Voucher Redemption: An unsuccessful server response yields an error dialog',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
gToken = "invalid";
await tester.enterText(find.byType(PlainTextField), "0000000000");
await tester.enterText(find.byType(MsisdnField), "0815029249");
await tester.tap(find.byType(PrimaryCardButton));
await tester.pump();
gToken = "validToken";
expect(find.byType(ErrorDialog), findsOneWidget);
});
}
For additional reference, I have also included the code for the VoucherRedemptionPage and VoucherRedemptionScreen below:
class VoucherRedemptionPage extends StatelessWidget {
final onSuccess;
final onFail;
const VoucherRedemptionPage({Key key, #required this.onSuccess, #required this.onFail})
: super(key: key);
#override
Widget build(BuildContext context) {
var _voucherRedemptionBloc = new VoucherRedemptionBloc();
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/" + gFlavor + "/primary_background.png"),
fit: BoxFit.cover),
),
child: new Scaffold(
backgroundColor: Colors.transparent,
appBar: new AppBar(
title: new Text(gDictionary.find("Redeem Voucher")),
),
body: new VoucherRedemptionScreen(
voucherRedemptionBloc: _voucherRedemptionBloc,
onSuccess: this.onSuccess,
onFail: this.onFail,
),
),
);
}
}
class VoucherRedemptionScreen extends StatefulWidget {
const VoucherRedemptionScreen({
Key key,
#required VoucherRedemptionBloc voucherRedemptionBloc,
#required this.onSuccess,
#required this.onFail,
}) : _voucherRedemptionBloc = voucherRedemptionBloc,
super(key: key);
final VoucherRedemptionBloc _voucherRedemptionBloc;
final onSuccess;
final onFail;
#override
VoucherRedemptionScreenState createState() {
return new VoucherRedemptionScreenState(
_voucherRedemptionBloc, onSuccess, onFail);
}
}
class VoucherRedemptionScreenState extends State<VoucherRedemptionScreen> {
final VoucherRedemptionBloc _voucherRedemptionBloc;
final onSuccess;
final onFail;
TextEditingController _msisdnController = TextEditingController();
TextEditingController _voucherPinController = TextEditingController();
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
VoucherRedemptionScreenState(
this._voucherRedemptionBloc, this.onSuccess, this.onFail);
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<VoucherRedemptionEvent, VoucherRedemptionState>(
bloc: _voucherRedemptionBloc,
builder: (
BuildContext context,
VoucherRedemptionState currentState,
) {
if (currentState is VoucherRedemptionInitial) {
_voucherPinController.text = currentState.scannedNumber;
return _buildFormCard();
}
if (currentState is VoucherRedemptionLoading) {
return Center(
child: CircularProgressIndicator(),
);
}
if (currentState is VoucherRedemptionSuccess) {
return SuccessDialog(
title: gDictionary.find("Voucher Redeemed Successfully"),
description: currentState.successMessage,
closeText: gDictionary.find("OK"),
closeAction: () {
this.onSuccess();
_voucherRedemptionBloc.dispatch(ResetVoucherRedemptionState());
},
);
}
if (currentState is VoucherRedemptionError) {
return ErrorDialog(
errorCode: currentState.errorCode,
errorMessage: currentState.errorMessage,
closeText: gDictionary.find("OK"),
closeAction: () {
this.onFail();
_voucherRedemptionBloc.dispatch(ResetVoucherRedemptionState());
},
);
}
},
);
}
Widget _buildFormCard() {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8), topRight: Radius.circular(8))),
padding: EdgeInsets.fromLTRB(12, 12, 12, 0),
width: double.infinity,
height: double.infinity,
child: _buildCardContent(),
);
}
Widget _buildCardContent() {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
gDictionary.find("Transaction Amount"),
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryColorDark,
fontWeight: FontWeight.bold),
),
Container(height: 16),
Form(
key: _formKey,
child: _buildFormContent(),
),
],
),
);
}
Column _buildFormContent() {
return Column(
children: <Widget>[
PlainTextField(
controller: _voucherPinController,
label: gDictionary.find("Voucher Number"),
required: true,
),
Container(height: 16),
MsisdnField(
controller: _msisdnController,
label: gDictionary.find("Mobile Number"),
required: true,
),
Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
SecondaryCardButton(
text: gDictionary.find("SCAN VOUCHER"),
onPressed: () {
_voucherRedemptionBloc.dispatch(
ScanBarcode(),
);
},
),
Container(
width: 8.0,
),
PrimaryCardButton(
text: gDictionary.find("REDEEM"),
onPressed: () {
if (_formKey.currentState.validate()) {
_voucherRedemptionBloc.dispatch(
RedeemVoucher(
_voucherPinController.text,
_msisdnController.text,
),
);
}
},
),
],
)
],
);
}
}
Found the problem. I was using the singleton pattern when creating an instance of the bloc - this caused states to persist across different widget objects. Very unlikely that anyone will encounter the same problem that I did but below is the code that I changed to mitigate the problem
Old problematic code:
class VoucherRedemptionBloc
extends Bloc<VoucherRedemptionEvent, VoucherRedemptionState> {
static final VoucherRedemptionBloc _voucherRedemptionBlocSingleton =
new VoucherRedemptionBloc._internal();
factory VoucherRedemptionBloc() {
return _voucherRedemptionBlocSingleton;
}
VoucherRedemptionBloc._internal();
//...
}
Updated working code:
class VoucherRedemptionBloc
extends Bloc<VoucherRedemptionEvent, VoucherRedemptionState> {
VoucherRedemptionBloc();
//...
}
That likely happens because your tests mutate some global variable but do not reset their value.
One way to make it safe is to always use setUp and tearDown instead of mutating variables directly the main scope:
int global = 0;
void main() {
final initialGlobalValue = global;
setUp(() {
global = 42;
});
tearDown(() {
global = initialGlobalValue;
});
test('do something with "global"', () {
expect(++global, 43);
});
test('do something with "global"', () {
// would fail without setUp/tearDown
expect(++global, 43);
});
}
Similarly, if a test needs to modify a variable, use addTearDown instead of manually resetting the value later in the test.
DON'T:
int global = 0;
test("don't", () {
global = 43;
expect(global, 43);
global = 0;
})
DO:
int global = 0;
test('do', () {
global = 43;
addTearDown(() => global = 0);
expect(global, 43);
});
This ensures that the value will always be reset even if the tests fails – so that other test functions normally.
In my case I wasn't setting skipOffstage: false, doing something like this worked for me:
expect(find.text('text', skipOffstage: false), findsNWidgets(2));