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();
Related
I am Flutter beginner. I'm trying to make News Feeder App.
I would like to Favorite Function Button on each List on my App.
But I don't know how to make it.
I tried to show favorite icons on each list. But It doesn't work.
I would like to select favorite button multiply.
Could you help it?
Here is the codes.
This code is that to show the news title and thumbnail as list.
I would like to put favorite Icon and work "active and non active function".
newslist_screen.dart
import 'package:flutter/material.dart';
import 'package:technewsfeeder/webview_screen.dart';
import 'package:technewsfeeder/fetch_newsdata.dart';
class NewsListScreen extends StatefulWidget {
// "static const" is always as this value.
static const String id = 'newslist_screen';
#override
_NewsListScreenState createState() => _NewsListScreenState();
}
class _NewsListScreenState extends State<NewsListScreen> {
Future<List<NewsDataList>> _savedList;
// Animation controller init method
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Tech News App'),
),
body: FutureBuilder(
future: fetchNewsData(),
builder: (context, snapshot) {
return snapshot.data != null
? listViewWidget(snapshot.data)
: Center(child: CircularProgressIndicator());
}),
);
}
Widget listViewWidget(List<NewsDataList> article) {
return Container(
child: ListView.builder(
itemCount: 20,
padding: const EdgeInsets.all(2.0),
itemBuilder: (context, position) {
return Card(
child: ListTile(
title: Text(
'${article[position].title}',
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
fontWeight: FontWeight.bold),
),
leading: Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
child: article[position].urlToImage == null
? Image(
image: AssetImage(''),
)
: Image.network('${article[position].urlToImage}'),
height: 100.0,
width: 100.0,
),
),
// *******
// I would like to put Favorite function here.
// *****
onTap: () {
print(article[position].url);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => WebViewScreen(url: article[position].url)),
);
},
),
);
}),
);
}
}
}
This is to get Json data from URL.
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';
class NewsDataList {
final String title;
final String url;
final String urlToImage;
NewsDataList({this.title, this.url, this.urlToImage});
factory NewsDataList.fromJson(Map<String, dynamic> json) {
return NewsDataList(
title: json['title'] as String,
url: json['url'] as String,
urlToImage: json['urlToImage'] as String,
);
}
}
Future<List<NewsDataList>> fetchNewsData() async {
List<NewsDataList> list;
String url = "http://newsapi.org/v2/top-headlines?country=jp&category=technology&apiKey=f289d460a5f94d4087d54cd187becceb";
var res = await http.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
print(res.body);
if(res.statusCode == 200){
var data = json.decode(res.body);
var rest = data["articles"] as List;
print(rest);
list = rest.map<NewsDataList>((json) => NewsDataList.fromJson(json)).toList();
return list;
} else {
throw Exception('Failed to load album');
}
}
V/r,
here is how you can about setting up the favourite button.
from list tile constructors we can use the trailing widget to set up a favorite button
const
ListTile(
{Key key,
Widget leading,
Widget title,
Widget subtitle,
Widget trailing,
bool isThreeLine: false,
bool dense,
EdgeInsetsGeometry contentPadding,
bool enabled: true,
GestureTapCallback onTap,
GestureLongPressCallback onLongPress,
bool selected: false}
)
First set up your list tile
ListTile(
leading: FlutterLogo(),
title: Text("article Title here"),
trailing: IconButton(
icon: Icon(
Icons.favorite,
color: _selectedIndex != null && _selectedIndex == position
? Colors.redAccent
: Colors.grey,
),
onPressed: (){
_onSelected(position);})
then how to change icon color on tap
int _selectedIndex;
_onSelected(int index) {
//https://inducesmile.com/google-flutter/how-to-change-the-background-color-of-selected-listview-in-flutter/
setState(() {
_selectedIndex = index;
});
}
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;
}())
});
}
I need to shape myUrl inside Future related to username which I got from myfirstpage, I can get the name and use it in my homepage title but I couldn't figured out how can I use it in myUrl(instead of "$_name"),
Probably I made a mistake with point to data with "pushNamed(MyHomePage.routeName);" actually I don't need that value in MyHomePage, I just need it for shape myUrl line, also I tried to make "_name" value as a global value etc just not couldn't succeed..
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
Future<bool> saveNamedPreference(String name) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString("name", name);
return prefs.commit();
}
Future<String> getNamePreferences() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String name = prefs.getString("name");
return name;
}
Payload payloadFromJson(String str) {
return Payload.fromJson(json.decode(str));
}
String payloadToJson(Payload data) {
return json.encode(data.toJson());
}
Future<Payload> getData() async{
String myUrl = 'http://lunedor.pythonanywhere.com/query?username=$_name';
http.Response response = await http.get(myUrl);
print(myUrl);
return response == null ? getData() : payloadFromJson(response.body);
}
class Payload {
String moviecast;
String moviedirectors;
String moviegenre;
String movieposterurl;
String movierating;
String movieruntime;
String moviesummary;
String movietitle;
String moviewriters;
String movieyear;
Payload({
this.moviecast,
this.moviedirectors,
this.moviegenre,
this.movieposterurl,
this.movierating,
this.movieruntime,
this.moviesummary,
this.movietitle,
this.moviewriters,
this.movieyear,
});
factory Payload.fromJson(Map<String, dynamic> json) => Payload(
moviecast: json["Actors"],
moviedirectors: json["Director"],
moviegenre: json["Genre"],
movieposterurl: json["Poster"],
movierating: json["imdbRating"],
movieruntime: json["Runtime"],
moviesummary: json["Plot"],
movietitle: json["Title"],
moviewriters: json["Writer"],
movieyear: json["Year"],
);
Map<String, dynamic> toJson() => {
"moviecast": moviecast,
"moviedirectors": moviedirectors,
"moviegenre": moviegenre,
"movieposterurl": movieposterurl.replaceAll('300.jpg', '900.jpg'),
"movierating": movierating,
"movieruntime": movieruntime,
"moviesummary": moviesummary,
"movietitle": movietitle,
"moviewriters": moviewriters,
"movieyear": movieyear,
};
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Random Movie',
theme: new ThemeData(
primarySwatch: Colors.grey,
),
home: new MyFirstPage(),
routes: <String, WidgetBuilder>{
MyHomePage.routeName: (context) => new MyHomePage(),
},
);
}
}
class MyFirstPage extends StatefulWidget {
#override
_MyFirstPageState createState() => new _MyFirstPageState();
}
class _MyFirstPageState extends State<MyFirstPage>{
var _controller = new TextEditingController();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
backgroundColor: Colors.blueGrey,
centerTitle: true,
title: new Text("Please enter your Trakt username",
style: new TextStyle(
fontSize: 18.0,
color: Colors.white,
),
),
),
body: new ListView(
children: <Widget>[
new ListTile(
title: new TextField(
controller: _controller,
),
),
new ListTile(
title: new RaisedButton(
child: new Text("Submit"),
onPressed:(){setState(() {
saveName();
});
}),
)
],
),
);
}
void saveName() {
String name = _controller.text;
saveNamedPreference(name).then((bool committed) {
Navigator.of(context).pushNamed(MyHomePage.routeName);
});
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
static String routeName = "/myHomePage";
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
Payload payload;
class _MyHomePageState extends State<MyHomePage> {
Modal modal = Modal();
bool isLoading = true;
String _name = "";
#override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {loadData();
WidgetsBinding.instance.addPostFrameCallback((_) => _refreshIndicatorKey.currentState.show());
getNamePreferences().then(updateName);
});
}
void loadData() async {
payload = await getData();
isLoading = false;
setState(() {});
print('${payload.movieposterurl.replaceAll('300.jpg', '900.jpg')}');
}
void updateName(String name) {
setState(() {
this._name = name;
ValueKey(_name);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar:
AppBar(
backgroundColor: Colors.blueGrey,
title:
isLoading ? Center(child: CircularProgressIndicator()):Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Center(
child: Text('${payload.movietitle}', maxLines: 2, textAlign: TextAlign.center,
style: new TextStyle(
fontSize: 18.0,
color: Colors.white,
),
),
),
Text('${payload.movieyear}' + " - " + _name + " - " + '${payload.movierating}',
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
fontStyle: FontStyle.italic,),
),
]
),
),
),
body:
isLoading ? Center(child: CircularProgressIndicator()):
RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: () async{payload = await getData();
isLoading = false;
setState(() {});
},
child: Center(
child:ListView(
shrinkWrap: true,
children: [
FittedBox(
alignment: Alignment.center,
child:
Image.network('${payload.movieposterurl.replaceAll('300.jpg', '900.jpg')}'),
),
]
)
)
),
bottomNavigationBar: isLoading ? Center(child: CircularProgressIndicator()):
BottomAppBar(
child: Container(
color: Colors.grey,
child: SizedBox(
width: double.infinity,
child:
FlatButton(
color: Colors.grey,
textColor: Colors.black,
onPressed: () {
modal.mainBottomSheet(context);
},
child: Text("Details",
style: TextStyle(fontSize: 16.0),),
),
),
),
),
);
}
}
class Modal {
mainBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
_createTile(
context, "Genre: " + payload.moviegenre + "\n" + "Runtime: " + payload.movieruntime, Icons.local_movies,
_action),
_createTile(
context, "Director: " + payload.moviedirectors,
Icons.movie_creation,
_action),
_createTile(
context, "Writer: " + payload.moviewriters,
Icons.movie,
_action),
_createTile(
context, "Cast: " + payload.moviecast, Icons.chrome_reader_mode,
_action),
_createTile(
context, "Summary: " + payload.moviesummary, Icons.recent_actors, _action),
],
),
);
}
);
}
ListTile _createTile(BuildContext context, String name, IconData icon,
Function action) {
return ListTile(
leading: Icon(icon),
title: Text(name),
onTap: () {
Navigator.pop(context);
action();
},
);
}
_action() {
print('action');
}
}
Not completely sure I got your question, but it should be enough to modify your getData() method to accept a String in parameters, at the moment getData() is a top-level function that doesn't know the _name value because is a private instance variable of _MyHomePageState
Future<Payload> getData(String name) async{
String myUrl = 'http://lunedor.pythonanywhere.com/query?username=$name';
http.Response response = await http.get(myUrl);
print(myUrl);
return response == null ? getData() : payloadFromJson(response.body);
}
And then in your loadData() method pass the correct value
void loadData() async {
payload = await getData(_name);
isLoading = false;
setState(() {});
print('${payload.movieposterurl.replaceAll('300.jpg', '900.jpg')}');
}
One last thing, you should add the "reload" logic when you change the name
void updateName(String name) {
setState(() {
isLoading = true;
this._name = name;
ValueKey(_name);
loadData();
});
}
Personally I think that the Payload variable should stay inside your _MyHomePageState class
sorry for the long explaination but I think I have to be very clear about the topic.
I have been running in this issue for last couple of weeks. I am using flutter_bloc package.
I have a simple Search page(ProposalSearchPage) with searchbox and listview. Listview gets build based on the search keyword which dispatches KeywordsearchEvent. on Clicking the setting icon it opens of ProposalSearchSetting page which is a dialog box page.
Based on this structure I have two events used in the bloc. One for the keyword search and another for the Advance filter search.
In advance searching page after applying filters. Inside a button there I dispatched FilterSearchEvent and have used Navigation.pop(context) to return to ProposalSearchPage.
Based on the state change The listview renders for both the searches, but for the Adavance filter search, the ProposalSearchSetting dialog box is partially visible. The filter button in there are clickable. It dismiss when I click backarrow button.
I don't know why Navigator.pop is adding page to stack instead of popping the stack.
#PROPOSALSEARCH PAGE
class ProposalSearchPage extends StatefulWidget {
final UserProfileBloc userProfileBloc;
final MenuBloc menuBloc;
final String authToken;
ProposalSearchPage({this.userProfileBloc, this.menuBloc, this.authToken})
: assert(menuBloc != null),
assert(userProfileBloc != null);
#override
_ProposalSearchPageState createState() => _ProposalSearchPageState();
}
class _ProposalSearchPageState extends State<ProposalSearchPage> {
UserProfileBloc get _userProfileBloc => widget.userProfileBloc;
List filteredProposal = [];
String get _authToken => widget.authToken;
MenuBloc get _menuBloc => widget.menuBloc;
ProposalSearchBloc _proposalSearchBloc;
String searchedKeyword = "";
int searchProposalPage = 1;
#override
void initState() {
_proposalSearchBloc =
ProposalSearchBloc(proposalRepository: ProposalListingRepo());
_menuBloc.dispatch(MenuResponseFetchedEvent());
super.initState();
}
#override
void dispose() {
_proposalSearchBloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Color(0xff2b57ff),
leading: IconButton(
icon: Icon(Icons.chevron_left),
onPressed: () {
Navigator.of(context).pop();
},
),
actions: <Widget>[
IconButton(
icon: new Icon(CupertinoIcons.gear_big),
onPressed: () {
/* Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProposalSearchSetting(
proposalSearchBloc: _proposalSearchBloc,
menuBloc: _menuBloc,
userProfileBloc: _userProfileBloc,
context: context),
fullscreenDialog: true,
),
);*/
showDialog<FilterProposalPost>(
context: context,
builder: (context) {
return ProposalSearchSetting(
proposalSearchBloc: _proposalSearchBloc,
menuBloc: _menuBloc,
userProfileBloc: _userProfileBloc,
context: context);
});
}),
],
title: Center(
child: Container(
width: 250.0,
height: 35.0,
decoration: BoxDecoration(
color: Colors.black12,
borderRadius: BorderRadius.all(Radius.circular(7.0))),
child: CupertinoTextField(
placeholder: 'search here.',
style: TextStyle(
color: Colors.white,
),
onSubmitted: (keyword) {
print(keyword);
searchedKeyword = keyword;
FilterProposalPost filterProposalPost =
_buildSearchQueryParameter(keyword);
// print(query);
_proposalSearchBloc.proposalFilterPostParam(filterProposalPost);
},
),
),
),
),
body: SearchListing(_proposalSearchBloc, _authToken),
);
}
FilterProposalPost _buildSearchQueryParameter(String keyword) {
return FilterProposalPost(
........
);
}
}
}
class SearchListing extends StatelessWidget {
final ProposalSearchBloc _proposalSearchBloc;
final String _authToken;
SearchListing(this._proposalSearchBloc, this._authToken);
#override
Widget build(BuildContext context) {
return BlocBuilder(
bloc: _proposalSearchBloc,
// ignore: missing_return
builder: (context, state) {
if (state is ProposalSearchFetchingState) {
return Center(
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation(Color(0xff2b57ff))),
);
} else if (state is ProposalSearchFetchedState) {
final filteredProposal = state.filteredProposal;
print(filteredProposal.length.toString);
return _buildSearchProposalList(filteredProposal);
}
},
);
}
Widget _buildSearchProposalList(List searchedProposals) {
return ListView.builder(
itemCount: searchedProposals.length + 1,
itemBuilder: (context, position) {
return position >= searchedProposals.length
? _buildLoaderListItem()
: ProposalCardFactory(
proposal: searchedProposals[position],
authToken: _authToken,
);
});
}
Widget _buildLoaderListItem() {
return Center(
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation(Color(0xff2b57ff))));
}
}
#ProposalSearchSettingPage
class ProposalSearchSetting extends StatefulWidget {
final UserProfileBloc userProfileBloc;
final ProposalSearchBloc proposalSearchBloc;
final MenuBloc menuBloc;
final BuildContext context;
final Function() notifyParent;
ProposalSearchSetting({this.notifyParent,
this.proposalSearchBloc,
this.userProfileBloc,
this.menuBloc,
this.context});
#override
_ProposalSearchSettingState createState() =>
_ProposalSearchSettingState();
}
class _ProposalSearchSettingState extends State<ProposalSearchSetting>
with SingleTickerProviderStateMixin {
UserProfileBloc get _userProfileBloc => widget.userProfileBloc;
ProposalSearchBloc get _proposalSearchBloc => widget.proposalSearchBloc;
List<String> selectedOptions = [];
String resultBy;
List<String> industries;
List<String> stages;
List<String> locations;
List<String> languages;
List<String> countries;
List<String> regionsValue = [];
MenuBloc get _menuBloc => widget.menuBloc;
Animation<double> animation;
AnimationController controller;
double startingPoint;
#override
void initState() {
super.initState();
}
#override
void dispose() {
_userProfileBloc.dispose();
_proposalSearchBloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
//double startingPoint = MediaQuery.of(context).size.height;
return MaterialApp(
theme: ThemeData(
buttonTheme: ButtonThemeData(
minWidth: 200.0,
height: 40.0,
buttonColor: Color(0xff2b57ff),
textTheme: ButtonTextTheme.primary)),
home: Scaffold(
body: BlocBuilder(
bloc: _menuBloc,
// ignore: missing_return
builder: (context, state) {
if (state is MenuResponseFetchedState) {
MenuListData _menuListData = state.menuListData;
return Padding(
padding: const EdgeInsets.only(top: 100.0),
child: Center(
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () async {
resultBy = await showDialog(
context: context,
builder: (context) {
return ResultBySearchDialog(
userProfileBloc: _userProfileBloc,
menuListData: _menuListData,
title: 'Result By:',
options: _menuListData.displayBy.values
.toList());
});
},
color: Color(0xff2b57ff),
child: Text(
'RESULT BY',
style: TextStyle(fontFamily: 'MyRaidPro'),
),
),
SizedBox(
height: 20,
),
RaisedButton(
onPressed: () async {
countries = await showDialog(
context: context,
builder: (context) {
return CountrySearchDialog(
userProfileBloc: _userProfileBloc,
menuListData: _menuListData,
title: 'Select Countries',
selectedOptions: selectedOptions,
onSelectedOptionListChanged: (options) {
selectedOptions = options;
print(selectedOptions);
});
});
},
color: Color(0xff2b57ff),
child: Text(
'COUNTRY',
style: TextStyle(fontFamily: 'MyRaidPro'),
),
),
SizedBox(
height: 20,
),
RaisedButton(
onPressed: () async {
industries = await showDialog(
context: context,
builder: (context) {
return IndustrySearchDialog(
menuListData: _menuListData,
title: 'Select Industries',
options: _menuListData.industries.values
.toList(),
selectedOptions: selectedOptions,
onSelectedOptionListChanged: (options) {
selectedOptions = options;
print(selectedOptions);
});
});
},
child: Text(
'INDUSTRIES',
style: TextStyle(fontFamily: 'MyRaidPro'),
),
),
SizedBox(
height: 20,
),
RaisedButton(
onPressed: () async {
stages = await showDialog(
context: context,
builder: (context) {
return StageSearchDialog(
context: context,
menuListData: _menuListData,
title: 'Select Stages',
options:
_menuListData.stages.values.toList(),
selectedOptions: selectedOptions,
onSelectedOptionListChanged: (options) {
selectedOptions = options;
print(selectedOptions);
});
});
},
child: Text(
'STAGES',
style: TextStyle(fontFamily: 'MyRaidPro'),
),
),
SizedBox(
height: 20,
),
RaisedButton(
onPressed: () async {
languages = await showDialog(
context: context,
builder: (context) {
return LanguageSearchDialog(
menuListData: _menuListData,
title: 'Select Languages',
options: _menuListData.languages.values
.toList(),
selectedOptions: selectedOptions,
onSelectedOptionListChanged: (options) {
selectedOptions = options;
print(selectedOptions);
});
});
},
child: Text(
'LANGUAGES',
style: TextStyle(fontFamily: 'MyRaidPro'),
),
),
SizedBox(
height: 20,
),
RaisedButton(
onPressed: () async {
locations = await showDialog(
context: context,
builder: (context) {
return LocationSearchDialog(
menuListData: _menuListData,
title: 'Select Locations',
options: _menuListData.locations.values
.toList(),
selectedOptions: selectedOptions,
onSelectedOptionListChanged: (options) {
selectedOptions = options;
print(selectedOptions);
});
});
},
child: Text(
'LOCATIONS',
style: TextStyle(fontFamily: 'MyRaidPro'),
),
),
SizedBox(
height: 40,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
ButtonTheme(
textTheme: ButtonTextTheme.primary,
minWidth: 60,
child: RaisedButton(
onPressed: () {
Navigator.of(this.widget.context).pop();
},
color: Color(0xff2b57ff),
child: Text(
'Cancel',
style: TextStyle(fontFamily: 'MyRaidPro'),
),
),
),
ButtonTheme(
textTheme: ButtonTextTheme.primary,
minWidth: 60,
child: RaisedButton(
onPressed: () {
_proposalSearchBloc.dispatch(ProposalFilterFetchEvent(advanceFilter:
FilterProposalPost(......)));
Navigator.pop(context);
print(("value from dialog" +
industries.toString()));
print(("value from dialog" +
stages.toString()));
print(("value from dialog" +
locations.toString()));
print(("value from dialog" +
languages.toString()));
},
color: Color(0xff2b57ff),
child: Text(
'Apply',
style: TextStyle(fontFamily: 'MyRaidPro'),
),
),
)
],
)
],
),
),
),
);
}
},
),
),
);
}
}
#BLOC
class ProposalSearchBloc
extends Bloc<ProposalSearchEvent, ProposalSearchState> {
final ProposalListingRepo proposalRepository;
List keywordSearchedProposalList = List();
List filteredProposalList = List();
ProposalSearchBloc({this.proposalRepository});
void proposalFilterPostParam(FilterProposalPost filterProposalPost) {
dispatch(ProposalSearchFetchEvent(filterProposalPost: filterProposalPost));
}
#override
ProposalSearchState get initialState => ProposalSearchFetchingState();
#override
Stream<ProposalSearchState> mapEventToState(event) async* {
try {
var filteredProposal;
print("proposal search even fired first time");
if (event is ProposalSearchFetchEvent) {
filteredProposal =
await proposalRepository.filterProposal(event.filterProposalPost);
} else if (event is ProposalFilterFetchEvent) {
print("filter event");
filteredProposal =
await proposalRepository.filterProposal(event.advanceFilter);
filteredProposalList.addAll(filteredProposal);
yield ProposalSearchFetchedState(filteredProposal: filteredProposal);
}
} catch (_) {
//print(error.toString());
yield ProposalSearchErrorState();
}
}
}
Finally I was able to solve the problem. I just forgot to use try catch blok in my bloc. This solves most of the problems of reloading the previous page after changing configuration from another page (DialogBox Box probably). I just had to make few changes in the Bloc code as:
#override
Stream<ProposalSearchState> mapEventToState(event) async* {
yield ProposalSearchFetchingState();
if (event is ProposalSearchFetchEvent) {
try {
print("proposal search");
filteredProposal =
await proposalRepository.filterProposal(event.filterProposalPost);
yield ProposalSearchFetchedState(
searchedProposal: filteredProposal);
} catch (error) {
print(error);
}
if (event is ProposalFilterFetchEvent) {
try {
print("proposal filtered");
filteredProposal =
await proposalRepository.filterProposal(event.filterProposalPost);
yield ProposalFilteredFetchedState(
filteredProposal: filteredProposal);
} catch (error) {
print(error.toString());
}
}
}
}
I'm making a command and control application using Flutter, and have come across an odd problem. The main status page of the app shows a list of stateful widgets, which each own a WebSocket connection that streams state data from a connected robotic platform. This worked well when the robots themselves were hardcoded in. However now that I'm adding them dynamically (via barcode scans), only the first widget is showing status.
Further investigation using the debugger shows that this is due to the fact that a state is only getting created for the first widget in the list. Subsequently added widgets are successfully getting constructed, but are not getting a state. Meaning that createState is not getting called for anything other than the very first widget added. I checked that the widgets themselves are indeed being added to the list and that they each have unique hash codes. Also, the IOWebSocketChannel's have unique hash codes, and all widget data is correct and unique for the different elements in the list.
Any ideas as to what could be causing this problem?
Code for the HomePageState:
class HomePageState extends State<HomePage> {
String submittedString = "";
StateContainerState container;
List<RobotSummary> robotList = [];
List<String> robotIps = [];
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
void addRobotToList(String ipAddress) {
var channel = new IOWebSocketChannel.connect('ws://' + container.slsData.slsIpAddress + ':' + container.slsData.wsPort);
channel.sink.add("http://" + ipAddress);
var newConnection = new RobotSummary(key: new UniqueKey(), channel: channel, ipAddress: ipAddress, state: -1, fullAddress: 'http://' + container.slsData.slsIpAddress + ':' + container.slsData.wsPort,);
scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text("Adding robot..."), duration: Duration(seconds: 2),));
setState(() {
robotList.add(newConnection);
robotIps.add(ipAddress);
submittedString = ipAddress;
});
}
void _onSubmit(String val) {
// Determine the scan data that was entered
if(Validator.isIP(val)) {
if(ModalRoute.of(context).settings.name == '/') {
if (!robotIps.contains(val)) {
addRobotToList(val);
}
else {
scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text("Robot already added..."), duration: Duration(seconds: 5),));
}
}
else {
setState(() {
_showSnackbar("Robot scanned. Go to page?", '/');
});
}
}
else if(Validator.isSlotId(val)) {
setState(() {
_showSnackbar("Slot scanned. Go to page?", '/slots');
});
}
else if(Validator.isUPC(val)) {
setState(() {
_showSnackbar("Product scanned. Go to page?", '/products');
});
}
else if (Validator.isToteId(val)) {
}
}
#override
Widget build(BuildContext context) {
container = StateContainer.of(context);
return new Scaffold (
key: scaffoldKey,
drawer: Drawer(
child: CategoryRoute(),
),
appBar: AppBar(
title: Text(widget.topText),
),
bottomNavigationBar: BottomAppBar(
child: new Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(icon: Icon(Icons.camera_alt), onPressed: scan,),
IconButton(icon: Icon(Icons.search), onPressed: _showModalSheet,),
],
),
),
body: robotList.length > 0 ? ListView(children: robotList) : Center(child: Text("Please scan a robot.", style: TextStyle(fontSize: 24.0, color: Colors.blue),),),
);
}
void _showModalSheet() {
showModalBottomSheet(
context: context,
builder: (builder) {
return _searchBar(context);
});
}
void _showSnackbar(String message, String route) {
scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text(message),
action: SnackBarAction(
label: 'Go?',
onPressed: () {
if (route == '/') {
Navigator.popUntil(context,ModalRoute.withName('/'));
}
else {
Navigator.of(context).pushNamed(route);
}
},),
duration: Duration(seconds: 5),));
}
Widget _searchBar(BuildContext context) {
return new Scaffold(
body: Container(
height: 75.0,
color: iam_blue,
child: Center(
child: TextField(
style: TextStyle (color: Colors.white, fontSize: 18.0),
autofocus: true,
keyboardType: TextInputType.number,
onSubmitted: (String submittedStr) {
Navigator.pop(context);
_onSubmit(submittedStr);
},
decoration: new InputDecoration(
border: InputBorder.none,
hintText: 'Scan a tote, robot, UPC, or slot',
hintStyle: TextStyle(color: Colors.white70),
icon: const Icon(Icons.search, color: Colors.white70,)),
),
)));
}
Future scan() async {
try {
String barcode = await BarcodeScanner.scan();
setState(() => this._onSubmit(barcode));
} on PlatformException catch (e) {
if (e.code == BarcodeScanner.CameraAccessDenied) {
setState(() {
print('The user did not grant the camera permission!');
});
} else {
setState(() => print('Unknown error: $e'));
}
} on FormatException{
setState(() => print('null (User returned using the "back"-button before scanning anything. Result)'));
} catch (e) {
setState(() => print('Unknown error: $e'));
}
}
}
Code snippet for the RobotSummary class:
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:test_app/genericStateSummary_static.dart';
import 'dart:convert';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:test_app/StateDecodeJsonFull.dart';
import 'dart:async';
import 'package:test_app/dataValidation.dart';
class RobotSummary extends StatefulWidget {
final String ipAddress;
final String _port = '5000';
final int state;
final String fullAddress;
final WebSocketChannel channel;
RobotSummary({
Key key,
#required this.ipAddress,
#required this.channel,
this.state = -1,
this.fullAddress = "http://10.1.10.200:5000",
}) : assert(Validator.isIP(ipAddress)),
super(key: key);
#override
_RobotSummaryState createState() => new _RobotSummaryState();
}
class _RobotSummaryState extends State<RobotSummary> {
StreamController<StateDecodeJsonFull> streamController;
#override
void initState() {
super.initState();
streamController = StreamController.broadcast();
}
#override
Widget build(BuildContext context) {
return new Padding(
padding: const EdgeInsets.all(20.0),
child: new StreamBuilder(
stream: widget.channel.stream,
builder: (context, snapshot) {
//streamController.sink.add('{"autonomyControllerState" : 3, "pickCurrentListName" : "69152", "plannerExecutionProgress" : 82, "pickUpcCode" : "00814638", "robotName" : "Adam"}');
return getStateWidget(snapshot);
},
),
);
}
#override
void dispose() {
streamController.sink.close();
super.dispose();
}
}
Based on what Jacob said in his initial comments, I came up with a solution that works and is a combination of his suggestions. The code solution he proposed above can't be implemented (see my comment), but perhaps a modification can be attempted that takes elements of it. For the solution I'm working with now, the builder call for HomePageState becomes as follows:
Widget build(BuildContext context) {
List<RobotSummary> tempList = [];
if (robotList.length > 0) {
tempList.addAll(robotList);
}
container = StateContainer.of(context);
return new Scaffold (
key: scaffoldKey,
drawer: Drawer(
child: CategoryRoute(),
),
appBar: AppBar(
title: Text(widget.topText),
),
bottomNavigationBar: BottomAppBar(
child: new Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(icon: Icon(Icons.camera_alt), onPressed: scan,),
IconButton(icon: Icon(Icons.search), onPressed: _showModalSheet,),
],
),
),
body: robotList.length > 0 ? ListView(children: tempList) : Center(child: Text("Please scan a robot.", style: TextStyle(fontSize: 24.0, color: iam_blue),),),
);
}
The problem is you are holding on to the StatefulWidgets between build calls, so their state is always the same. Try separating RobotSummary business logic from the view logic. Something like
class RobotSummary {
final String ipAddress;
final String _port = '5000';
final int state;
final String fullAddress;
final WebSocketChannel channel;
StreamController<StateDecodeJsonFull> streamController;
RobotSummary({
#required this.ipAddress,
#required this.channel,
this.state = -1,
this.fullAddress = "http://10.1.10.200:5000",
}) : assert(Validator.isIP(ipAddress));
void init() => streamController = StreamController.broadcast();
void dispose() => streamController.sink.close();
}
And then in your Scaffold body:
...
body: ListView.builder(itemCount: robotList.length, itemBuilder: _buildItem)
...
Widget _buildItem(BuildContext context, int index) {
return new Padding(
padding: const EdgeInsets.all(20.0),
child: new StreamBuilder(
stream: robotList[index].channel.stream,
builder: (context, snapshot) {
//streamController.sink.add('{"autonomyControllerState" : 3, "pickCurrentListName" : "69152", "plannerExecutionProgress" : 82, "pickUpcCode" : "00814638", "robotName" : "Adam"}');
return getStateWidget(snapshot); // not sure how to change this.
},
),
);
}