Creating an app in Flutter for a local resturant/pub/bistro and I want to display a card above all other cards in the menu to show temporary messages/deals. My implementation relies on a menu_screen and a "handler" of sorts (not fully developed mind you).
But there's two problems
It displays as a card within a card
the information is displayed twice when it should only be shown once at the top of the screen
Here's what it looks like when run on a device for testing:
Pastebin for menu_screen: enter link description here
// sets up the menu screen for our program
// imports
import 'package:flutter/material.dart';
import 'package:elehouseapp/handlers/food_menu_handler.dart';
// set up class
class Menus extends StatelessWidget{
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: TabBar(
labelColor: Colors.black,
indicatorColor: Colors.black,
tabs: <Widget>[
Tab(
text: 'Food',
),
Tab(
text: 'Drink',
),
]
),
body: TabBarView(
children: <Widget>[
FoodHandler(),
//Text("Food"),
Text("Drink"),
],
),
),
);
}
}
Pastebin for food_menu_handler: enter link description here
import 'package:flutter/material.dart';
// Generates an 2D array of food items and places elements into cards
// Some data
final items = ['test','test2'];
final desc = ["Loreum Isplum","Other Test"];
final price = [2.20,20.00];
// Handles array data and puts into cards
class FoodHandler extends StatelessWidget{
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index){
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Visibility(
visible: true,
child: Card(
child: ListTile(
title: Text("COVID-19"),
subtitle: Text("Menu restrictions are in place"),
),
)
),
ListTile(
title: Text(items[index]),
subtitle: Text(desc[index]+"\n£"+price[index].toString()),
),
ButtonBar(
children: <Widget>[
FlatButton(
child: Text("Add to Basket"),
onPressed: null,
),
],
),
],
),
);
}
);
}
}
EDIT I've un-nested the card (as pointed out). Hopefully this may explain what I have with explanations of what I'm trying to do
You could change the ListView.builder in a normal ListView and generate the list of its children using conditions and loops inside the list as follow:
// Generates an 2D array of food items and places elements into cards
// Some data
final items = ['test', 'test2'];
final desc = ["Loreum Isplum", "Other Test"];
final price = [2.20, 20.00];
// Handles array data and puts into cards
class FoodHandler extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView(
children: [
if (1 == 1) // TODO your condition here
Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Card(
child: ListTile(
title: Text("COVID-19"),
subtitle: Text("Menu restrictions are in place"),
),
),
],
),
),
for (int index = 0; index < items.length; index++)
Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text(items[index]),
subtitle: Text(desc[index] + "\n£" + price[index].toString()),
),
ButtonBar(
children: <Widget>[
FlatButton(
child: Text("Add to Basket"),
onPressed: null,
),
],
),
],
),
),
],
);
}
}
I marked with a TODO the line where you have to insert the condition for the first "alert".
Also I suggest you to use a class to represent the list items instead of having three different lists and "joining" them with the index, it's a more convenient and elegant solution
Related
I have the follow structure: A list of Expansion tiles > clicking on it, opens another list of ExpansionTiles > Clicking in one of them, it should open some widgets according to a SQL query.
The problem is, when I tap in the first Expansion Tile it loads all the widgets from all the Expansion Tiles inside the first option making the query very slow. I want to only load the widgets when I tap in the second one (loading only the necessary ones)
Here is the code:
1st list:
class ListItemsScreen extends StatefulWidget {
#override
_ListItemsScreenState createState() => _ListItemsScreenState();
}
class _ListItemsScreenState extends State<ListItemsScreen> {
final Widget appBar = AppBar(
title: Text('ITEMS'),
actions: [
Builder(
builder: (context) => IconButton(
icon: Icon(Icons.shopping_bag_outlined),
onPressed: () {
Navigator.of(context)
.pushNamed(ROUTE_CHART);
},
),
),
],
);
#override
Widget build(BuildContext context) {
final List items = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: appBar,
body: items == null || items.isEmpty ?
Center(child: Text("0 items here"),)
:
ListView(
children: [
...items.map<Widget>(
(item) {
return ExpansionTile(
leading: Image.asset(ASSET_IMAGE,
fit: BoxFit.cover
),
title: Text('${item.code} | ${item.description}'),
subtitle:
Text('${item.color}'),
children: [
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: ProductWidget(item),
),
],
),
);
},
)
],
)
);
2nd list (ProductWidget):
class ProductWidget extends StatefulWidget {
final Product product;
ProductWidget(this.produto);
#override
_ProductWidgetState createState() => _ProductWidgetState();
}
class _ProdutoGradeWidgetState extends State<ProdutoGradeWidget> {
#override
Widget build(BuildContext context) {
CustomScrollView(
slivers: [
StreamBuilder(
stream: product.stream,
builder: (ctx, snapshot) {
return SliverList(
delegate: SliverChildBuilderDelegate((ctx, i) {
if (i == 0) {
return Column(
children: [
Align(
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
'I HAVE THIS PRODUCT IN THESE COLORS',
style: TextStyle(
fontSize: 20,
color:
Theme.of(context).textTheme.caption.color,
),
)
),
),
const SizedBox(height: 20.0),
ProductColorsWidget(color: snapshot.data[i]),
],
);
} else if (i == snapshot.data.length - 1) {
return Column(
children: [
ProductColorsWidget(color: snapshot.data[i]),
const SizedBox(height: 20.0),
Padding(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'Qtd',
style: TextStyle(
fontSize: 16,
color:
Theme.of(context).textTheme.caption.color,
),
),
),
),
const SizedBox(height: 20.0),
],
);
}
return ProductColorsWidget(color: snapshot.data[i]);
}, childCount: snapshot.data.length),
);
}
},
),
],
);
}
}
}
}
3rd part (Product Colors Widget where I list the second Expansion Tiles):
class ProductColorsWidget extends StatelessWidget {
final ColorProduct color;
ProdutoCorGradeWidget({this.color});
#override
Widget build(BuildContext context) {
return ExpansionTile(
maintainState: true,
tilePadding: EdgeInsets.all(15.0),
title: Text(
'${color.id} - ${color.description}',
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(fontWeight: FontWeight.w600),
),
childrenPadding: EdgeInsets.all(10.0),
children: [
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...color.sizes.map<Widget>(
(item) {
return Column(
children: [
Expanded(
child: Text(
item.description, textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold),
)
),
...item.prices.map<Widget>((size) {
return PricesWidget( //here it should show the widgets according to the second ExpansionTiles
color: color,
size: size
);
})
]
);
}
)
],
)
)
],
);
}
}
So, to be clear, what I want is: First It lists the products (with expansionTiles), expanding one it should show the second Tiles (with sizes) and after selecting one it should show the widgets.
..But what is happening now is: List the products and when I select one the app loads all the widget from all the second 'expansionTiles' making it slow to show the second list. What should I do?
I think the problem is with
ExpansionTile(
maintainState: true,
.....
)
I had a similar issue in which I had an ExpansionTile that was its own child and it caused a stack overflow because it was building them all. After setting maintainState to false the problem was solved.
You might have to adapt your state management according to that the children state may not be saved
I'm trying to add a functionality to each question, represented as a ListTile, so that it can upvote or downvote a question, and show the net votes, just like the one that is used on stack overflow. My current implementation does a bottom overflow for each ListTile.
Card(
child: new Column(
children: <Widget>[
new ListTile(
leading: Column(
children: <Widget>[
FlatButton(
child: Icon(Icons.arrow_drop_up),
onPressed: () {},
),
StreamBuilder<DocumentSnapshot>(
stream: RoomDbService(widget.roomName, widget.roomID)
.getQuestionVotes(widget.questionID),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
print(snapshot.data.data["votes"]);
return Text("${snapshot.data.data["votes"]}");
}
},
),
FlatButton(
child: Icon(Icons.arrow_drop_down),
onPressed: () {},
),
],
), // shows votes of this qn on the left of the tile
title: Text(text),
trailing: FlatButton(
child: Icon(Icons.expand_more),
onPressed: toggleExpansion,
),
)
],
),
);
My previous implementation (which I forgot how it looked like) made it look like a row of an up button, the vote count, and the down button. How do I do it properly?
Check out this example Taking you example I have made some modifications in the code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: HomePage());
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Card(
child: new Column(
children: <Widget>[
SizedBox(
height: 10,
),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
children: <Widget>[
InkWell(onTap: () {}, child: Icon(Icons.arrow_drop_up)),
Text(
"your Text,
style: TextStyle(fontSize: 10),
),
InkWell(onTap: () {}, child: Icon(Icons.arrow_drop_down)),
],
),
Text('You stream text'),
FlatButton(
child: Icon(Icons.expand_more),
onPressed: () {},
),
],
),
)
],
),
)),
);
}
}
Let me know if it works.
You can make custom widget for your desired layout using Row....
But if you still want to use ListTile, then you have to make somechanges in your code,
ListTile's height we can't set as we want, it's depends on subtitle and isThreeLine property.
So you can get some more height if you add subtitle, and with isThreeLine : true, gives your subtitle more height to fit in ListTile....
For your case you need change leading widget....Use InkWell instead of FlatButton....
Make some changes in CircularProgressIndicator.
use small sized icon for upvote/downvote and use small Text for count, otherwise it will overflow again.
See the code below or play with it at DartPad ListTile_StackOverFlow.
Card(
child: ListTile(
leading: Column(
children:[
InkWell(
child: Icon(Icons.arrow_drop_up),
onTap: () {}
),
Container(
height: 8,
width:8,
child: Center(child: CircularProgressIndicator(strokeWidth :2))
),
InkWell(
child: Icon(Icons.arrow_drop_down),
onTap: () {}
),
]
),
title: Text('Titled text'),
trailing: Icon(Icons.more_vert),
),
);
Better solution is use Row and column and make your own custom Widget that looklike ListTile.... see the official document, here you can see an example which has CustomListTile class which creates the custom looking ListTile( which is not directly using ListTile )....
My advise : You should make your custom class as like above Documentation's CustomListTile class
I'm new to Flutter/Dart, so maybe the problem I'have is just lack of knowledge. My goal is to build a card with an horizontal header on top of the card and then the card should display a list of item/value pairs vertically, wrapping them to a new column if the device is large enough. I've added a Column, for two children (the header and the Wrap), but if it's embedded in a column there's no wrapping at all.
I tried a lot of combinations but I didn't find a solution. If I remove the column, the Wrap widget works perfectly.
class TestApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return
MaterialApp(
title: 'Wrap Test',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: TestScreen(),
);
}
}
class TestScreen extends StatelessWidget {
/*
Builds a single item/value pair
*/
Widget _text(int i) {
var container = Container(
height: 50,
child: Row(
children: <Widget>[
Container(
width: 200,
child: Text(
'item $i',
),
),
Text(
'value $i',
),
],
),
);
return container;
}
/*
Builds a list of item/value pairs
*/
List<Widget> _items(int n) {
List<Widget> widgetList = [];
for (int i = 1; i <= n; i++) {
widgetList.add(_text(i));
}
return widgetList;
}
/*
This way Wrap widget isn't working as I thought...the reason is that it seems bounded by
the column and the column does not expands itself due to wrapping
*/
Widget buildWrapNotWorking(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Wrap Test"),
),
body: Card(
color: Colors.yellow,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Header",
),
Wrap(
direction: Axis.vertical,
runSpacing: 50,
crossAxisAlignment: WrapCrossAlignment.start,
spacing: 20,
children: _items(20),
),
],
),
),
);
}
/*
This way Wrap widget is working, because I removed the column. But I need to have a card header
on top of the card.
*/
Widget buildWrapWorkingButNoHeader(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Wrap Test"),
),
body: Card(
color: Colors.yellow,
child: Wrap(
direction: Axis.vertical,
runSpacing: 100,
crossAxisAlignment: WrapCrossAlignment.start,
spacing: 20,
children: _items(20),
),
),
);
}
#override
Widget build(BuildContext context) {
return buildWrapNotWorking(context);
// return buildWrapWorkingButNoHeader(context);
}
}
I'm expecting that calling buildWrapNotWorking(context) will work as desired.
The problem is similar to that one:
How to wrap row items in a card with flutter
Simply wrap you Wrap widget with - Expanded - this was it will get enough space in column.
code:
Widget buildWrapNotWorking(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Wrap Test"),
),
body: Card(
color: Colors.yellow,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Header",
),
Expanded(
child: Wrap(
direction: Axis.vertical,
runSpacing: 50,
crossAxisAlignment: WrapCrossAlignment.start,
spacing: 20,
children: _items(20),
),
),
],
),
),
);
}
I have added a few widgets inside the ListView. So that I can scroll all widgets. Now I required to add one more widget to the ListView to load the list of comments. I can not add the ListView inside the ListView. And moreover, I do not require the separate scroll for my comments. It should be scroll along with the widgets inside the ListView. So I planned to add the Column instead of ListView. Could any help to add my comments dynamically in the Columns?
new Expanded(
child:
new ListView(
shrinkWrap: true,
children: <Widget>[
// Title
new Padding(padding: const EdgeInsets.only(
top: 10.00, left: 10.00),
child: new Text(
_feed.title, textAlign: TextAlign.start,),
),
// content
new Container(
child: new Text(
_feed.content, textAlign: TextAlign.start,),
),
// Comments List will go here
],
),
),
If you have the comments data already, simply create a List, then pass it to the children property of the Column. Something like:
var commentWidgets = List<Widget>();
for (var comment in comments) {
commentWidgets.Add(Text(comment.text)); // TODO: Whatever layout you need for each widget.
}
…
new Expanded(
child:
new ListView(
shrinkWrap: true,
children: <Widget>[
// Title
new Padding(padding: const EdgeInsets.only(
top: 10.00, left: 10.00),
child: new Text(
_feed.title, textAlign: TextAlign.start,),
),
// content
new Container(
child: new Text(
_feed.content, textAlign: TextAlign.start,),
),
// Comments List will go here
Column(children: commentWidgets,),
],
),
),
If you don't have the comments data already and need to fetch it, use a FutureBuilder to build the UI once the future completes.
Another way:
return Column(
children: [
for(String item in list) Text(item);
]);
You can also mix static and dynamic fields eaisly in this case:
return Column(
children: [
for(String item in list) Text(item),
Text("Static text control"),
]);
By maintaining a reference to the Column object, the .children field/property can be referenced after the Column object has been declared - like so:
Column someColumn = Column(
children: [],
);
someColumn.children.add(Text('Hello 123'));
Currently(2021-06), Flutter 2.x implemented null-safe.
The answer from #Gene Bo should work but needs little modification.
This should work.
var pwdWidgets = <Widget>[];
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: this.pwdWidgets,
),
ElevatedButton(
onPressed: (){
setState((){
this.pwdWidgets.add(Text("Hello World"),);
});
},
child: Text("click me"),
),
In Addition to #Derek Lakin's Answer which worked for me too, it is also required to call setState(), if you need to update the comments and reload.
var commentWidgets = List<Widget>();
for (var comment in comments) {
commentWidgets.Add(Text(comment.text)); // TODO: Whatever layout you need foreach widget.
}
setState(() {
});
import 'package:flutter/material.dart';
class LoginRegisterPage extends StatefulWidget {
#override
_LoginRegisterPageState createState() => _LoginRegisterPageState();
}
class _LoginRegisterPageState extends State<LoginRegisterPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("App"),
),
body: new Container(
margin: EdgeInsets.all(15.0),
child: new Form(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: createInputs() + createButtons(),
),
),
),
);
}
List<Widget> createInputs(){
return{
SizedBox(height: 10.0),
logo(),
SizedBox(height: 20.0),
new TextFormField(
decoration: new InputDecoration(labelText: 'Email'),
),
SizedBox(height: 10.0),
new TextFormField(
decoration: new InputDecoration(labelText: 'Passwors')
),
SizedBox(height: 20.0),
};
}
Widget logo(){
return new Hero(
tag:'hero',
child: new CircleAvatar(
backgroundColor:Colors.transparent,
radius: 110.0,
child: Image.asset("images\logo.PNG"),
),
);
}
List<Widget> createButtons(){
return{
new RaisedButton(
child: new Text("Login", style: new TextStyle(fontSize: 20.0),),
textColor: Colors.pink,
onPressed: () {
},
),
new FlatButton(
child: new Text("Already not have an account?", style: new TextStyle(fontSize: 14.0),),
textColor: Colors.white,
onPressed: () {
},
)
};
}
}
Yes you can add dynamic child on column widgets.
class CategoriesItemWidget extends StatelessWidget {
final List<String> list;
const CategoriesItemWidget({Key? key, required this.list})
: super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: list
.map((string) => Text(string))
.toList(),
);
}
}
I've been playing around trying to learn flutter and programming in general. I have a problem here that I have not been able to find a solution for on the forums
or anywhere else. I want to display the details of a FireStore Document on this view. The previous view is List view from Flutter only showing the Title.
The previous view(main) passes the document ID into this view as "partID." I have been able to successfully query that document for a snapshot and even print
out particular details. However, when I try to add the variables as the "existing text" in a form field, it doesn't recognize them.
What am I missing?
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'ItemData.dart';
class View extends StatefulWidget {
final String partID;
View({Key key, this.partID}): super (key: key);
#override
ViewState createState() => ViewState();
}
class ViewState extends State<View> {
Data newData = new Data();
#override
Widget build(BuildContext context) {
getItem();
return new Scaffold(
//CreateWidget()
appBar: AppBar(
backgroundColor: Colors.black,
title: Text("Item Data"),
),
body:
new Column(
children: <Widget>[
Flexible(
flex: 0,
child: Center(
child: Form(
//key: this._formKey,
child: Flex(
direction: Axis.vertical,
children: <Widget>[
ListTile(
title: TextFormField(
initialValue: newData.title,
decoration: new InputDecoration(
icon: new Icon(Icons.edit),
),
)
),
ListTile(
title: TextFormField(
initialValue: title,
decoration: new InputDecoration(
icon: new Icon(Icons.edit),
),
)
),
new Text("${widget.partID}"),
],
),
),
),
),
],
)
);
}
Future getItem() async {
DocumentSnapshot snapshot = await Firestore.instance.collection('items').document('${widget.partID}').get();
String title = snapshot['title'];
String location = snapshot.data['location'].toString();
print('${title}');
print('${location}');
}
}
For this you should be using something like a future builder as your body. It still allows loading asynchronously and you can also show a loading indicator. It also allows you to access all your values from the database within the child widgets.
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text(title)),
body: new FutureBuilder(
future: Firestore.instance.collection('items').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
return new Column(
children: <Widget>[
Flexible(
flex: 0,
child: Center(
child: Form(
//key: this._formKey,
child: Flex(
direction: Axis.vertical,
children: <Widget>[
ListTile(
title: TextFormField(
initialValue: snapshot.data["title"],
decoration: new InputDecoration(
icon: new Icon(Icons.edit),
),
)),
ListTile(
title: TextFormField(
initialValue: title,
decoration: new InputDecoration(
icon: new Icon(Icons.edit),
),
)),
new Text("${widget.partID}"),
],
),
),
),
),
],
);
}),
);
}
This is an example of how your code would be laid out when using a FutureBuilder.