How to call setState in a extended class? - flutter

class Cari extends AnaState implements InterHttp {
String token = Tokenlar.token();
Future<Void> getCariHareket() async {
final response = await get(
"http://148.111.156.214:36555/Api/Customers/CustomerActionById/3",
headers: {HttpHeaders.authorizationHeader: "bearer $token",HttpHeaders.acceptHeader: "application/json"},
);
if (response.statusCode == 200)
{
List<CariHareketListe> hareketListe=(json.decode(response.body) as List).map((i) =>
CariHareketListe.fromJson(i)).toList();
//Map<String,dynamic> map = json.decode(response.body);
setState(() {
provider = hareketListe;
});
}
else
{
throw Exception('Cari hareket listesi yükleme başarısız oldu');
}
}
}
i want to be able to call setstate in this class but it gives me this error:
FlutterError (setState() called in constructor: Cari#ed627(lifecycle state: created, no widget, not mounted)
This happens when you call setState() on a State object for a widget that hasn't been inserted into the widget tree yet. It is not necessary to call setState() in the constructor, since the state is already assumed to be dirty when it is initially created.)
This is the main dart i want to setstate in cari then i want that data go into Icwidgetlar to be able to Return a list view to Anastate
import 'package:flutter/material.dart';
import 'package:guven_avize/ApiMetodlar/CariMetod.dart';
import 'Veriler/CariVeriler.dart';
class Anamenu
{
Anamenu()
{
}
}
class Ana extends StatefulWidget{
#override
AnaState createState() => AnaState();
}
class IcWidgetlar
{
List<CariHareketListe> provider = new List<CariHareketListe>();
ListView icyapi(int secilenTab)
{
if (secilenTab == 0)
{
Cari cari = new Cari();
cari.veriCek(1);
ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: provider.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
color: Colors.amber,
child: Center(child: Text('Entry ${provider[index]}')),
);
}
);
}
}
}
class AnaState extends State<Ana> with IcWidgetlar {
IcWidgetlar widgetlar = new IcWidgetlar();
int selectedIndex = 0;
static const TextStyle optionStyle = TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Cari',
style: optionStyle,
),
Text(
'Stok',
style: optionStyle,
),
Text(
'Sipariş',
style: optionStyle,
),
];
void _onItemTapped(int index) {
setState(() {
selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () async => false,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Color.fromRGBO(106, 112, 222, 50),
title: _widgetOptions.elementAt(selectedIndex),
),
body: widgetlar.icyapi(selectedIndex),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.face),
title: Text('Cari'),
),
BottomNavigationBarItem(
icon: Icon(Icons.inbox),
title: Text('Stok'),
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart),
title: Text('Sipariş'),
),
],
currentIndex: selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
));
}
}

Why are you not returning the result instead?
Instead of returning Future, return Future>, and get it from other class.
You can even use FutureBuilder to wait until the response arrive.
Call the function in initState
#override
void initState() {
super.initState();
getCariHareket().then( (list) {
setState((){
provider = list;
}())
});
}

Related

How to block the repeated item in flutter?

I saw this code in KindaCode. This is link ( https://www.kindacode.com/article/flutter-hive-database/#single__comments ) . I want the added item not to be added again. How can I do that? This code, we just add items and delete and olsa upgrade. We just write the name and quantity and adding the item. But if i write the same name as the other one, adds again.
// main.dart
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
await Hive.openBox('shopping_box'); //verileri icerisinde barındırıcak kutuyu olusturma
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'KindaCode.com',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: const HomePage(),
);
}
}
// Home Page
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Map<String, dynamic>> _items = [];
final _shoppingBox = Hive.box('shopping_box');
#override
void initState() {
super.initState();
_refreshItems(); // Load data when app starts
}
// Get all items from the database
void _refreshItems() {
final data = _shoppingBox.keys.map((key) {
final value = _shoppingBox.get(key);
return {"key": key, "name": value["name".toLowerCase()], "quantity": value['quantity']};
}).toList(); //verileri listede gosterme
setState(() {
_items = data.reversed.toList(); //en sondan en eskiye dogru siralamak icin reversed
// we use "reversed" to sort items in order from the latest to the oldest
});
}
// Create new item
Future<void> _createItem(Map<String, dynamic> newItem) async {
await _shoppingBox.add(newItem); //yeni veri olusturma
_refreshItems(); // update the UI
}
// Retrieve a single item from the database by using its key
// Our app won't use this function but I put it here for your reference
// Update a single item
Future<void> _updateItem(int itemKey, Map<String, dynamic> item) async {
await _shoppingBox.put(itemKey, item); //tablo icerisine veriyi koyma
_refreshItems(); // Update the UI
}
// Delete a single item
Future<void> _deleteItem(int itemKey) async {
await _shoppingBox.delete(itemKey);
_refreshItems(); // update the UI
// Display a snackbar
ScaffoldMessenger.of(context).showSnackBar( //ekranin alt kisminda itemin silindigini belirtme
const SnackBar(content: Text('An item has been deleted')));
}
// TextFields' controllers
final TextEditingController _nameController = TextEditingController();
final TextEditingController _quantityController = TextEditingController();
void _showForm(BuildContext ctx, int? itemKey) async { //yeni item eklerken ve butona basildiginda tetiklenen flotingbutton
// itemKey == null -> create new item
// itemKey != null -> update an existing item
if (itemKey != null) { //itemi guncelleme
final existingItem =
_items.firstWhere((element) => element['key'] == itemKey);
_nameController.text = existingItem['name'];
_quantityController.text = existingItem['quantity'];
}
showModalBottomSheet(
context: ctx,
elevation: 5,
isScrollControlled: true,
builder: (_) => Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(ctx).viewInsets.bottom,
top: 15,
left: 15,
right: 15),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
TextField(
controller: _nameController,
decoration: const InputDecoration(hintText: 'Name'),
),
const SizedBox(
height: 10,
),
TextField(
controller: _quantityController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Quantity'),
),
const SizedBox(
height: 20,
),
ElevatedButton( //here the create or upgrade the item
onPressed: () async {
// Save new item
if (itemKey == null) { //yeni item
if() {
_createItem({
"name": _nameController.text,
"quantity": _quantityController.text
});
}
}
// update an existing item
if (itemKey != null) {
_updateItem(itemKey, {
'name': _nameController.text.trim(),
'quantity': _quantityController.text.trim()
});
}
// Clear the text fields
_nameController.text = '';
_quantityController.text = '';
Navigator.of(context).pop(); // Close the bottom sheet
},
child: Text(itemKey == null ? 'Create New' : 'Update'),
),
const SizedBox(
height: 15,
)
],
),
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('KindaCode.com'),
),
body: _items.isEmpty
? const Center(
child: Text(
'No Data',
style: TextStyle(fontSize: 30),
),
)
: ListView.builder(
// the list of items
itemCount: _items.length,
itemBuilder: (_, index) {
final currentItem = _items[index];
return Card(
color: Colors.orange.shade100,
margin: const EdgeInsets.all(10),
elevation: 3,
child: ListTile(
title: Text(currentItem['name']),
subtitle: Text(currentItem['quantity'].toString()),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
// Edit button
IconButton(
icon: const Icon(Icons.edit), //düzenleme butonu
onPressed: () =>
_showForm(context, currentItem['key'])),
// Delete button
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _deleteItem(currentItem['key']),
),
],
)),
);
}),
// Add new item button
floatingActionButton: FloatingActionButton( //ekranin alt kosesinde bulunan ekleme butonu
onPressed: () => _showForm(context, null),
child: const Icon(Icons.add),
),
);
}
} ```

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();

Using json file to give appbar logo for flutter. Facing Loading issue while navigation

I wanted to show appbar logo for all the pages from a json file. Now the issue is if I use Futurebuilder
then in every page load, appbar logo awaits before showing. I tried to use shared preference but having issues. Maybe I am doing it wrong. I am a newbie in flutter. If possible please give the answer in simple way so I can understand. Or anyone can help me by creating that part for appbar. That will be helpful.
Here is my json file for logo
import 'dart:convert';
import 'package:models/app_logo.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import './home_screen.dart';
import './login_screen.dart';
import '../constants.dart';
import '../screens/courses_screen.dart';
import '../screens/my_courses_screen.dart';
import '../screens/my_wishlist_screen.dart';
import '../screens/account_screen.dart';
import '../widgets/filter_widget.dart';
import '../providers/auth.dart';
import 'package:http/http.dart' as http;
class TabsScreen extends StatefulWidget {
#override
_TabsScreenState createState() => _TabsScreenState();
}
class _TabsScreenState extends State<TabsScreen> {
List<Widget> _pages = [
HomeScreen(),
LoginScreen(),
LoginScreen(),
LoginScreen(),
];
var _isInit = true;
var _isLoading = false;
int _selectedPageIndex = 0;
bool _isSearching = false;
final searchController = TextEditingController();
Future<AppLogo> futureLogo;
Future<AppLogo> fetchMyLogo() async {
var url = BASE_URL + '/app_logo';
try {
final response = await http.get(url);
print(response.body);
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
print(response.body);
return AppLogo.fromJson(jsonDecode(response.body));
}
// print(extractedData);
} catch (error) {
throw (error);
}
}
#override
void initState() {
super.initState();
this.fetchMyLogo();
// Provider.of<Auth>(context).tryAutoLogin().then((_) {});
}
#override
void didChangeDependencies() {
if (_isInit) {
setState(() {
_isLoading = true;
});
final _isAuth = Provider.of<Auth>(context, listen: false).isAuth;
if (_isAuth) {
_pages = [
HomeScreen(),
MyCoursesScreen(),
MyWishlistScreen(),
AccountScreen(),
];
}
}
_isInit = false;
super.didChangeDependencies();
}
void _handleSubmitted(String value) {
final searchText = searchController.text;
if (searchText.isEmpty) {
return;
}
searchController.clear();
Navigator.of(context).pushNamed(
CoursesScreen.routeName,
arguments: {
'category_id': null,
'seacrh_query': searchText,
'type': CoursesPageData.Search,
},
);
// print(searchText);
}
void _selectPage(int index) {
setState(() {
_selectedPageIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: kSecondaryColor, //change your color here
),
title: !_isSearching
? FutureBuilder<AppLogo>(
future: fetchMyLogo(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: Container(),
);
} else {
if (snapshot.error != null) {
return Center(
child: Text("Error Occured"),
);
} else {
return Image.network(
snapshot.data.darkLogo,
fit: BoxFit.contain,
height: 27,
);
}
}
},
)
: TextFormField(
decoration: InputDecoration(
labelText: 'Search Here',
prefixIcon: Icon(
Icons.search,
color: Colors.grey,
),
),
controller: searchController,
onFieldSubmitted: _handleSubmitted,
),
backgroundColor: kBackgroundColor,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.search,
color: kSecondaryColor,
),
onPressed: () {
setState(() {
_isSearching = !_isSearching;
});
}),
],
),
body: _pages[_selectedPageIndex],
floatingActionButton: FloatingActionButton(
onPressed: () => _showFilterModal(context),
child: Icon(Icons.filter_list),
backgroundColor: kDarkButtonBg,
),
bottomNavigationBar: BottomNavigationBar(
onTap: _selectPage,
items: [
BottomNavigationBarItem(
backgroundColor: kBackgroundColor,
icon: Icon(Icons.school),
title: Text('Course'),
),
BottomNavigationBarItem(
backgroundColor: kBackgroundColor,
icon: Icon(Icons.shopping_basket),
title: Text('My Course'),
),
BottomNavigationBarItem(
backgroundColor: kBackgroundColor,
icon: Icon(Icons.favorite_border),
title: Text('Wishlist'),
),
BottomNavigationBarItem(
backgroundColor: kBackgroundColor,
icon: Icon(Icons.account_circle),
title: Text('Account'),
),
],
backgroundColor: kBackgroundColor,
unselectedItemColor: kSecondaryColor,
selectedItemColor: kSelectItemColor,
currentIndex: _selectedPageIndex,
type: BottomNavigationBarType.fixed,
),
);
}
}
There are some ways you can do to reduce the load time of the logo in your AppBar. Since this AppBar is common between the tabs, you should only load it once and avoid loading again every time the tab is changed.
First is to use StreamBuilder instead of FutureBuilder to reduce the number of loads.
// Create a StreamController
final _controller = StreamController<AppLogo>();
// Run fetchMyLogo() in your initState() like in your code
// In your fetchMyLogo(), add the result to the stream
fetchMyLogo() async {
// ... other lines
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
var logo = AppLogo.fromJson(jsonDecode(response.body));
_controller.add(logo);
}
// Then, listen to this logo in your StreamBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// ... other lines
title: !_isSearching
? StreamBuilder<AppLogo>(
stream: _controller.stream,
builder: (context, snapshot) {
// ... other lines
Second is to use the cached_network_image instead of Image.network, so that your logo image is cached, which reduce load time for network images.
return CachedNetworkImage(
imageUrl: snapshot.data.darkLogo,
fit: BoxFit.contain,
height: 27,
);
Small note: For each of the page in your _pages list, if you want the page to persist (not reload after every tab change), you can use the AutomaticKeepAliveClientMixin to persist the state of each page, or use IndexedStack like:
// The IndexedStack will load all pages initially
body: IndexedStack(
children: _pages,
index: _selectedPageIndex,
),
// ... other lines

Accessing a stream inside a Nested StreamBuilder in Flutter

I am having an issue calling a StreamBuilder inside another StreamBuilder which is located inside the onPressed field of RasedButton widget. Basically, I am trying to access a stream (after adding data to it) inside the inner StreamBuilder, but the execution does not call this part of the code:
Widget submitButton(BuildContext ctx, AccountBloc bloc){
return StreamBuilder(
stream: bloc.submitValid,
builder: (context, snapshot){
return SizedBox(
width: double.infinity,
height: 60,
child: RaisedButton(
color: HexColor("0072b1"),
child: Text("Sign in", style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: Colors.white
),
),
onPressed:() {
if(snapshot.hasData){
bloc.loginUser();
// DEBUGGING
print(bloc.isAuthenticated.listen((val) { print("debug isAuthenticated: $val"); }));
StreamBuilder(
stream: bloc.isAuthenticated,
builder: (ctx, snapshotA){
print("here");
if(!snapshotA.hasData){
return Text("loading....");
}
print("***${snapshotA.data}****");
if(snapshotA.data == false){
return navSignUpScreen(ctx);
}else if (snapshotA.data == false){
print("here");
return navHomeScreen(ctx);
}
return navSignUpScreen(ctx);
}
);
}
},
),
);
}
);
}
The BLOC part is as follows:
final _isAuthenticated = BehaviorSubject<bool>();
Stream<bool> get isAuthenticated => _isAuthenticated.stream;
void loginUser() async {
var inMemory = InMemoryProvider();
inMemory.newInMemoryProvider();
UserModel userResult = await _repository.loginUser(_email.value, _password.value);
if(userResult.status == 200){
// save TOKEN
UserModel userModel = UserModel.fromJsonToModel(userResult.data);
bool res = await inMemory.store(userModel.id, userModel.token);
_isAuthenticated.sink.add(res);
}
_userLoginResponse.sink.add(userResult);
}
The navHomeScreen definition is as simple as this:
class HomeScreen extends StatefulWidget {
createState() {
return HomeState();
}
}
class HomeState extends State<HomeScreen> {
int _currentIndex = 0;
final List<Widget> _children = [
AllReportsScreen(), UserProfileScreen()
];
Widget build(context) {
return Scaffold(
body: _children[_currentIndex],
bottomNavigationBar: mainBottomNavigatorBar(),
);
}
Widget mainBottomNavigatorBar() {
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: onTabTapped,
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
backgroundColor: Colors.black45,
icon: new Icon(Icons.note),
title: new Text('Reports', style: TextStyle(fontSize: 15.0)),
),
BottomNavigationBarItem(
backgroundColor: Colors.black45,
icon: new Icon(Icons.label_important),
title: new Text('Attention', style: TextStyle(fontSize: 15.0)),
),
BottomNavigationBarItem(
backgroundColor: Colors.black45,
icon: new Icon(Icons.person_pin),
title: new Text('Profile', style: TextStyle(fontSize: 15.0)),
),
],
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
Using combineLatest from rxdart to combine two Streams isAuthenticated and submitValid into one Stream:
class LoginState {
final bool isAuthenticated;
final bool submitValid;
}
final state$ = Rx.combineLatest(
bloc.isAuthenticated,
bloc.submitValid,
(isAuth, valid) => LoginState(isAuth, valid),
);
StreamBuilder<LoginState>(
stream: state$,
builder: (context, snapshot) {
final state = snapshot.data;
if (state == null) return ...;
if (!state.submitValid) return ...;
if (state.isAuthenticated) return ...;
}
)

FutureBuilder and async function in flutter

I have a problem with FutureBuilder, it refresh and execute code again when there is a change like when i change value of radio button, i click on radio and it reloads all futurebuilder it seems.
EDIT : i have corrected the problem and here is my solution, i am not sure it works all time
My full code is :
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:async';
// Create a Form widget.
class Affiche_grille extends StatefulWidget {
#override
_Affiche_grille_State createState() {
return _Affiche_grille_State();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class _Affiche_grille_State extends State<Affiche_grille> {
#override
final _formKey = GlobalKey<FormState>();
List<String> radioValues = [];
Future<List<Match>> grid;
Future <List<Match>> Grille_display() async {
// SERVER LOGIN API URL
var url = 'http://www.axis-medias.fr/game_app/display_grid.php';
// Store all data with Param Name.
var data = {'id_grille': 1};
// Starting Web API Call.
var response = await http.post(url, body: json.encode(data));
// Getting Server response into variable.
var jsondata = json.decode(response.body);
List<Match> Matchs = [];
for (var u in jsondata) {
Match match = Match(u["equipe1"],u["equipe2"],u["type_prono"]);
Matchs.add(match);
radioValues.add("N");
}
return Matchs;
}
void initState() {
grid = Grille_display();
super.initState();
}
#override
Widget build(BuildContext context) {
final appTitle = 'MONEYFREE';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(appTitle),
),
body: Container(
child:
FutureBuilder(
future: grid,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Container (
child: Center(
child: Text("Chargement en cours...")
)
);
}
else {
List<Match> values = snapshot.data;
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
DataTable(
columnSpacing: 20,
columns: [
DataColumn(
label: Text("Libelle Match"),
numeric: false,
tooltip: "",
),
DataColumn(
label: Text("1"),
numeric: false,
tooltip: "",
),
DataColumn(
label: Text("N"),
numeric: false,
tooltip: "",
),
DataColumn(
label: Text("2"),
numeric: false,
tooltip: "",
),
],
rows:
List.generate(values.length, (index) {
return DataRow(
cells: [
DataCell(
Text(values[index].equipe1.toString() + " - " + values[index].equipe2.toString()),
),
DataCell(
Radio(
value: "1",
groupValue: radioValues[index],
onChanged: (val) {
setState(() {
radioValues[index] = val;
print('Change 1');
print(radioValues);
});
},
),
),
DataCell(
Radio(
value: "N",
groupValue: radioValues[index],
onChanged: (val) {
setState(() {
radioValues[index] = val;
print('Change N');
print(radioValues);
});
},
),
),
DataCell(
Radio(
value: "2",
groupValue: radioValues[index],
onChanged: (val) {
setState(() {
radioValues[index] = val;
print('Change 2');
print(radioValues);
});
},
),
),
]
);
}).toList(),
),
Center(
child: RaisedButton(
color: Colors.green,
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(9, 9, 9, 9),
child: Text('VALIDER VOTRE GRILLE'),
onPressed: () {
Valide_grille();
},
),
),
],
)
);
};
},
),
),
),
);
}
Future Valide_grille() async{
// For CircularProgressIndicator.
bool visible = false ;
// Showing CircularProgressIndicator.
setState(() {
visible = true ;
});
// SERVER LOGIN API URL
var url = 'http://www.axis-medias.fr/game_app/valide_grid.php';
var concatenate='';
radioValues.forEach((item){
concatenate=concatenate+item;
});
// Store all data with Param Name.
var data = {'id_membre':1, 'id_grille':1,'result':concatenate};
print (data);
var grille_encode=jsonEncode(data);
print(grille_encode);
// Starting Web API Call.
var response = await http.post(url, body: grille_encode);
print(response.body);
// Getting Server response into variable.
var message = json.decode(response.body);
// If the Response Message is Matched.
if(message == 'OK')
{
print('VALIDATION DE LA GRILLE OK');
// Hiding the CircularProgressIndicator.
setState(() {
visible = false;
});
}else{
// If Email or Password did not Matched.
// Hiding the CircularProgressIndicator.
setState(() {
visible = false;
});
// Showing Alert Dialog with Response JSON Message.
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text(message),
actions: <Widget>[
FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
}
class Match {
final String equipe1;
final String equipe2;
final String typeprono;
const Match(this.equipe1, this.equipe2, this.typeprono);
}
You can copy paste run full code below
Reason
didUpdateWidget of the FutureBuilder state is being called every time a rebuild is issued. This function checks if the old future object is different from the new one, and if so, refires the FutureBuilder.
https://github.com/flutter/flutter/issues/11426#issuecomment-414047398
Solution
Future _future;
#override
void initState() {
// TODO: implement initState
_future = Grille_display();
}
...
child: FutureBuilder(
future: _future,
working demo
full code
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:async';
// Create a Form widget.
class Affiche_grille extends StatefulWidget {
#override
_Affiche_grille_State createState() {
return _Affiche_grille_State();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class _Affiche_grille_State extends State<Affiche_grille> {
#override
final _formKey = GlobalKey<FormState>();
List<String> radioValues = [];
Future<List<Match>> Grille_display() async {
// SERVER LOGIN API URL
var url = 'http://www.axis-medias.fr/game_app/display_grid.php';
// Store all data with Param Name.
var data = {'id_grille': 1};
// Starting Web API Call.
var response = await http.post(url, body: json.encode(data));
// Getting Server response into variable.
var jsondata = json.decode(response.body);
List<Match> Matchs = [];
for (var u in jsondata) {
Match match = Match(u["equipe1"], u["equipe2"], u["type_prono"]);
Matchs.add(match);
}
return Matchs;
}
Future _future;
#override
void initState() {
// TODO: implement initState
_future = Grille_display();
}
#override
Widget build(BuildContext context) {
final appTitle = 'MONEYFREE';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(appTitle),
),
body: Container(
child: FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Container(
child: Center(child: Text("Chargement en cours...")));
} else {
List<Match> values = snapshot.data;
values.forEach((m) {
radioValues.add("N");
//like N or something
});
print('valeur radio après initialisation');
print(radioValues);
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
DataTable(
columnSpacing: 20,
columns: [
DataColumn(
label: Text("Libelle Match"),
numeric: false,
tooltip: "",
),
DataColumn(
label: Text("1"),
numeric: false,
tooltip: "",
),
DataColumn(
label: Text("N"),
numeric: false,
tooltip: "",
),
DataColumn(
label: Text("2"),
numeric: false,
tooltip: "",
),
],
rows: List.generate(values.length, (index) {
return DataRow(cells: [
DataCell(
Text(values[index].equipe1.toString() +
" - " +
values[index].equipe2.toString()),
),
DataCell(
Radio(
value: "1",
groupValue: radioValues[index],
onChanged: (val) {
setState(() {
radioValues[index] = val;
print('Change 1');
print(radioValues);
});
},
),
),
DataCell(
Radio(
value: "N",
groupValue: radioValues[index],
onChanged: (val) {
setState(() {
radioValues[index] = val;
print(radioValues);
});
},
),
),
DataCell(
Radio(
value: "2",
groupValue: radioValues[index],
onChanged: (val) {
setState(() {
radioValues[index] = val;
print(radioValues);
});
},
),
),
]);
}).toList(),
),
Center(
child: RaisedButton(
color: Colors.green,
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(9, 9, 9, 9),
child: Text('VALIDER VOTRE GRILLE'),
onPressed: () {
Valide_grille();
},
),
),
],
));
}
;
},
),
),
),
);
}
Future Valide_grille() async {
// For CircularProgressIndicator.
bool visible = false;
// Showing CircularProgressIndicator.
setState(() {
visible = true;
});
// SERVER LOGIN API URL
var url = 'http://www.axis-medias.fr/game_app/valide_grid.php';
// Store all data with Param Name.
var data = jsonEncode(radioValues);
print(radioValues);
// Starting Web API Call.
var response = await http.post(url, body: json.encode(data));
// Getting Server response into variable.
var message = json.decode(response.body);
// If the Response Message is Matched.
if (message == 'OK') {
print('VALIDATION DE LA GRILLE OK');
// Hiding the CircularProgressIndicator.
setState(() {
visible = false;
});
} else {
// If Email or Password did not Matched.
// Hiding the CircularProgressIndicator.
setState(() {
visible = false;
});
// Showing Alert Dialog with Response JSON Message.
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text(message),
actions: <Widget>[
FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
}
class Match {
final String equipe1;
final String equipe2;
final String typeprono;
const Match(this.equipe1, this.equipe2, this.typeprono);
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Affiche_grille(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}