Flutter: combining two widgets - flutter

My problem is like that:
Widget buildBottem(MyCart ordercart) {
//return buildItemsList(ordercart); // this is a Expanded
//return buildPriceInfo(ordercart); //this is a Row
return Container(
child: Column(
children: <Widget>[
buildItemsList(ordercart), //show items ordered
Divider(),
buildPriceInfo(ordercart),
]
)
);
}
In the above code, I can successfully return either buildItemsList(ordercart) or buildPriceInfo(ordercart) from the function with correct results if I uncomment the respective statement. However, if I try to combine both together as a Column, the result is a blank. The function is called within a FutureBuilder:
return Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
alignment: Alignment.center,
width: double.infinity,
child: Container(
width: 90,
height: 8,
decoration: ShapeDecoration(
shape: StadiumBorder(), color: Colors.black26),
),
),
buildTitle(ordercart),
Divider(),
Container(
child: FutureBuilder<Widget>(
future: retrieveItemsFromFirebase(ordercart),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new CircularProgressIndicator();
default:
if (ordercart.cartItems.length <= 0)
return noItemWidget();
else
return buildBottom(ordercart);
}
}
)
),
SizedBox(height: 8),
]
) //addToCardButton(ordercart, context),
);
}
This is in a Web-Firebase application so it is difficult to debug because every time I have to modify the index.html so that it can use Firebase.
I am including the screenshots:
With 'return buildItemsList(ordercart);'
With 'return buildPriceInfo(ordercart);'
and the code of the two implementations:
Widget buildItemsList(MyCart cart) {
return Expanded(
child: ListView.builder(
itemCount: cart.cartItems.length,
physics: BouncingScrollPhysics(),
itemBuilder: (context, index) {
return Card(
child: ListTile(
leading: CircleAvatar(
backgroundImage:
NetworkImage(cart.cartItems[index].food.image)),
title: Text('${cart.cartItems[index].food.name}',
style: subtitleStyle),
subtitle: Text('\$ ${cart.cartItems[index].food.price}'),
trailing: Text('x ${cart.cartItems[index].quantity}',
style: subtitleStyle),
),
);
},
),
);
}
and
Widget buildPriceInfo(MyCart cart) {
double total = 0;
for (CartItem cartModel in cart.cartItems) {
total += cartModel.food.price * cartModel.quantity;
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('Total:', style: headerStyle),
Text('\$ ${total.toStringAsFixed(2)}', style: headerStyle),
],
);
}
and the implementation of buildTitle(cart):
Widget buildTitle(cart) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('Your Order', style: headerStyle),
],
);
}

The reason why the Widgets buildItemsList() and buildPriceInfo() doesn't appear on screen as you expect is because both Widgets doesn't have bounds defined, and therefore could fill the entire screen. What you can do here is set bounds on those Widgets by using either Expanded or Container with defined height.

Related

How can I add a Container after a FutureBuilder?

How can you add more Containers or Widgets after using FutureBuilder?
I've been trying to combine these 2 blocks of code into one but can't figure out how to do so. I need the 2 buttons from code-block 2 to be added after this ListView.builder. Or does this have to be on 2 separate pages?
#override
Widget build(BuildContext context) =>
Scaffold(
body:
FutureBuilder<ListResult>(
future: futureFiles,
builder: (context, snapshot) {
if (snapshot.hasData) {
final files = snapshot.data!.items;
return ListView.builder(
itemCount: files.length,
itemBuilder: (context, index) {
final file = files[index];
double? progress = downloadProgress[index];
return ListTile(
title: Text(file.name),
subtitle: progress != null
? LinearProgressIndicator(
value: progress,
backgroundColor: Colors.black26,
)
: null,
trailing: IconButton(
icon: const Icon(
Icons.download,
color: Colors.white,
),
onPressed: () => downloadFile(index, file),
));
});
} else if (snapshot.hasError) {
return const Center(child: Text('Error occurred'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
)
);
}
I want to combine the following code into the code above. But can't quite figure out how to do that.
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if(pickedFile != null)
Expanded(
child: Container(
child: Center(
child: Image.file(File(pickedFile!.path!),width:double.infinity,fit: BoxFit.cover,),
//child: Text(pickedFile!.name),
)
)
),
if (pickedFile == null)
Expanded(
child: Container(
child: Center(
displayFiles()
)
)
),
Row(
mainAxisAlignment : MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.all(20.0),
child: SizedBox(
height:40,
width: 150,
child:
ElevatedButton(
child: Text('Select File'),
onPressed: selectFile,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
textStyle: const TextStyle(fontSize: 20),
),
),
),
),
SizedBox(
height:40,
width: 150,
child: ElevatedButton(
child: Text('Upload File'),
onPressed: uploadFile,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
textStyle: const TextStyle(fontSize: 20)
),
),
),
],
),
buildProgress(),
],
),
),
);
}
I tried wrapping the buttons inside a Container but I can't figure out where to place the Container in the first block of code.
Do like this
Column(
children: [
Expanded(
child: FutureBuilder<ListResult>()),
//Second page code
Center()
]
)
You may return a Column widget from the builder of FutureBuilder instead of returning a ListView, and make ListView the first child of that Column. The buttons can be defined as second, third....etc children as you please.
itemCount: files.length + 3;
final file = files[index+3];
if(index==0){
(some widget)
}
else if(index==1){
(some widget)
}
if(index==2){
(some widget)
}else return ListTile()

List view childs get out of container

I am trying to make a list view only occupy part of the screen, but it keeps growing till the end not respecting the contianer constraints. I tried to use a sizedbox too but it didn' work. List tiles outside the container are shown without any widget inside, but the background is shown anyways
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: pedidos,
builder: (context, AsyncSnapshot<List<Pedido>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: ListView.builder(
itemCount: snapshot.data!.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Hero(
tag:
"pedidos_card${snapshot.data![index].idPedido}",
child: ListTile(
tileColor: Colors.white,
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle),
child: Center(
child: Text(
style: Theme.of(context)
.textTheme
.headlineSmall,
"${snapshot.data![index].idPedido}"),
),
),
title: Text(
'Pedido: ${snapshot.data![index].idPedido}'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 10),
Text(
'Estado: ${snapshot.data![index].estadoPedido.last.tipoEstadoPedido.name}'),
SizedBox(height: 10),
Text(
"Cliente: ${snapshot.data![index].cliente.nombre}")
],
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
trailing: Checkbox(
value: pedidosSeleccion
.contains(snapshot.data![index]),
onChanged: (bool? value) {
// value = checkboxList[index];
// setState(() {});
},
),
onTap: () {
bool isSelected = pedidosSeleccion
.contains(snapshot.data![index]);
if (isSelected) {
pedidosSeleccion
.remove(snapshot.data![index]);
} else {
pedidosSeleccion.add(snapshot.data![index]);
}
setState(() {});
},
),
));
}),
),
ElevatedButton(
onPressed: () {}, child: Text('Ver ultima milla')),
],
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
});
}
}
example
you can use Expanded instead of Sizedbox
eg:-
Column(
children:[
Expanded(flex:9,
child: ListView(
padding: const EdgeInsets.only(top: 10.0),
children: snapshot.map((data) => _buildListItem(context, data)).toList(),
),
),
Expanded(flex:1,
child:
ElevatedButton(
// fill in required params
),
)
])

Cannot put PageView into Column in Flutter

I want to put a small PageView into a Column, but I got error RenderBox was not laid out: RenderRepaintBoundary#db5f8 relayoutBoundary=up14 NEEDS-PAINT. The PageView will hold inconsistent data so I cannot set the height for it. Can anyone help me? This is my code:
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
bottomNavigationBar: BottomAppBar(
child: Container(
height: 70,
color: Colors.white,
),
),
body: FutureBuilder(
future: Data.getProductDetails(id: widget.product.id),
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator(color: Colors.grey));
} else {
List iList = [];
iList.add(widget.product.mainImg);
for (ProductImages i in snapshot.data.imgList) {
iList.add(i.image);
}
return CustomScrollView(
slivers: [
SliverAppBar(...),
SliverToBoxAdapter(
child: SingleChildScrollView(
padding: EdgeInsets.all(15),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(...),
Row(...),
SizedBox(height: 10),
brandField(snapshot),
SizedBox(height: 5),
categoryField(snapshot),
SizedBox(height: 5),
tagsField(snapshot),
SizedBox(height: 20),
pageIndicator(),
Expanded(
child: PageView(
children: [
Container(height: 400, color: Colors.red),
Container(height: 600, color: Colors.blue),
],
),
)
],
),
),
),
],
);
}
},
),
);
}
You get this error because PageView and other widgets like ListView require size constraints from its parents and Column do not provide constraints to its children. So to solve the issue you can wrap you PageView inside Flexible or Expanded like so:
Column(
children:[
Expanded(child:PageView(),),
]
)
For better understanding of constrains in flutter, read: Understanding Constrains: Flutter.dev

Flutter Visibility with Condition (How can I hide only one widget with condition)

I have a problem with using Visibility widget.
What I want to do is hiding certain listview's by depending user's click on my Row. But when User click on row, all Listview is showing. When clicked again, all listview widget's are going away.
What I want to do is with images:
This is how my page looks
This is what happens when I click on arrow button or "Sezon 1" Text
This is what I want to do when I click on arrow button or "Sezon 1" Text
What I'm trying to do is When I click Season 2, it will show season 2 episodes. When I click season 3 it will show season 3 episodes etc.
Here is my code (I know It's a little messy for right now, apologize for that) :
GestureDetector is working same for every click.
bool viewVisible = false;
void hideWidget() {
setState(() {
viewVisible = !viewVisible;
print(viewVisible);
});
StreamBuilder<QuerySnapshot>(
stream: seasons.snapshots(),
builder:
(BuildContext context, AsyncSnapshot asyncSnapshot) {
if (asyncSnapshot.hasError) {
return Center(
child:
Text('Error'));
} else {
if (asyncSnapshot.hasData) {
List<DocumentSnapshot> listOfDocumentSnap =
asyncSnapshot.data.docs;
return Padding(
padding: const EdgeInsets.only(top: 0, left: 0),
child: Align(
alignment: Alignment.topLeft,
child: Column(
children: [
ListView.builder(
shrinkWrap: true,
itemCount: listOfDocumentSnap.length,
itemBuilder: (context, index) {
var episodes = firestore
.collection('shows')
.doc(data.id)
.collection('Seasons')
.doc(listOfDocumentSnap[index].id)
.collection('Episodes');
return Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(
top: 0, left: 18, right: 18),
child: GestureDetector(
onTap: () {
hideWidget();
},
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(
8),
border: Border.all(
color: Colors.pink)),
child: Row(
children: [
SizedBox(
width: 20,
),
Text(
listOfDocumentSnap[index]
.get('name')
.toString()
.toUpperCase(),
style: TextStyle(
fontSize: 20,
color:
Colors.grey[800],
fontWeight:
FontWeight.w700),
),
Spacer(flex: 1),
Icon(
Icons.arrow_drop_down,
size: 45,
color: Colors.pink,
),
],
),
),
),
),
StreamBuilder<QuerySnapshot>(
stream: episodes.snapshots(),
builder: (BuildContext context,
AsyncSnapshot asyncSnapshot) {
if (asyncSnapshot.hasError) {
return Center(
child: Text(
'Error'));
} else {
if (asyncSnapshot.hasData) {
List<DocumentSnapshot>
listOfDocumentSnap =
asyncSnapshot.data.docs;
return Padding(
padding:
const EdgeInsets.only(
top: 5, left: 18.0),
child: Visibility(
visible: viewVisible,
child: Align(
alignment:
Alignment.topLeft,
child: Column(
children: [
ListView.builder(
shrinkWrap: true,
itemCount:
listOfDocumentSnap
.length,
itemBuilder:
(context,
index) {
return ListTile(
onTap: () {
setState(
() {
selectedIndex =
index;
});
},
trailing:
Icon(Icons
.play_arrow),
title: Text(listOfDocumentSnap[
index]
.id
.toString()),
);
/* return Text(
listOfDocumentSnap[
index]
.get(
'name'));*/
},
),
],
),
),
),
);
} else {
return Center(
child:
CircularProgressIndicator());
}
}
},
),
SizedBox(
height: 30,
)
],
);
},
),
],
),
),
);
} else {
return Center(child: CircularProgressIndicator());
}
}
},
),
Sorry, I did not check what is wrong with your code. But, instead of writing your own hide/show logic for each row, try to use Flutter's ExpansionTile widget. It will handle the expanded/collapsed states for you:
https://medium.com/flutterdevs/expansion-tile-in-flutter-d2b7ba4a1f4b
There are two different way I know to handle widget visibility. Create a bool to switch visibility. I'm using _show.
Using if condition
inside (column or any widget)
if (_show) Container(),
Using Visibility Widget
Column(
children: [
Visibility(
visible: _show,
child: Container(),
),
)

Error when trying to make portion of page scroll

I keep getting errors when I try to make a portion of the page be able to scroll by adding SingleChildScrollView.
So this is how it looks like now:
So the portion in between the red lines is what I want to be able to scroll as I would be adding more things in later. Here is my current code :
class _NonAdminFlightPageState extends State<NonAdminFlightPage> {
#override
void initState() {
super.initState();
}
String _dateSelected = null;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: blue,
body:
SingleChildScrollView(
child: Center(
child:Stack(
children: <Widget>[
Align(
alignment: Alignment.center,
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
"assets/images/FlightBackground.jpg",
),
fit: BoxFit.fitHeight,
),
)
),
),
Align(
alignment: Alignment.center,
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration:BoxDecoration(color: blueTrans),),
),
Align(
alignment:Alignment.center,
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width/1.2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(flex:2,child: SizedBox(),),
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection("Users")
.document(widget.userEmail)
.collection("Flight Data")
.document("Courses")
.collection(widget.courseAbbr)
.snapshots(),
builder: (context, snapshot){
if (snapshot.hasError) return new Text('${snapshot.error}');
if(snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(white),));
}
return Theme(
data: ThemeData(canvasColor: blackTrans2, brightness: Brightness.dark),
child:Container(
decoration: BoxDecoration(
color: blackTrans,
borderRadius: BorderRadius.all(Radius.circular(5.0)),
),
child:DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton(
value: _dateSelected,
hint: AutoSizeText(NA_FLIGHT_PAGE_DROPDOWN, style: TextStyle(color: white,),textAlign: TextAlign.center,),
isDense: false,
onChanged: (String newValue){
setState(() {
_dateSelected = newValue;
});
},
items: snapshot.data.documents.map((DocumentSnapshot document){
return DropdownMenuItem<String>(
value: document.documentID,
child: AutoSizeText(document.documentID, style: TextStyle(color: white,),textAlign: TextAlign.center,),
);
}).toList(),
),
),
)
)
);
},
),
Expanded(child: SizedBox(),),
Expanded(
flex: 25,
child:StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection("Users")
.document(widget.userEmail)
.collection("Flight Data")
.document("Courses")
.collection(widget.courseAbbr)
.document(_dateSelected).snapshots(),
builder: (context, snapshot){
if (snapshot.hasError) {
return new Text('${snapshot.error}');
} else if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(white),));
} else {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(white),));
default:
var doc = snapshot.data;
// can execute animation here to make select flight glow
if (_dateSelected == null) {
return (Center(child: SizedBox(),));
}
return Column(
children: <Widget>[
Expanded(
flex: 8,
child: Container(
decoration: BoxDecoration(
color: blackTrans,
borderRadius: BorderRadius.all(Radius.circular(5.0)),
),
child: Column(
children: <Widget>[
Expanded(child: SizedBox(),),
Expanded(flex:1,child:AutoSizeText(NA_FLIGHT_PAGE_PATTERN, style: TextStyle(color: white,),textAlign: TextAlign.center,),),
Expanded(child:Divider(color: white,)),
Expanded(flex:8,child:ListView.builder(
padding: EdgeInsets.only(top: 0.0),
scrollDirection: Axis.vertical,
shrinkWrap: false,
itemCount: _dateSelected == null ? 0 : doc["patterns"].length ,
itemBuilder: (BuildContext context, int index) {
var patterns = doc["patterns"];
return buildPatternTile(patterns[index]);
}
),),
Expanded(child: SizedBox(),),
],
),
),
),
],
);
}
}
},
),
),
Expanded(child: SizedBox(),),
],
),
),
),
],
)
)
)
);
}
What I've tried:
Expanded(
flex: 25,
child:SingleChildScrollView(
child:StreamBuilder<DocumentSnapshot>(
.....
I've tried to wrap the 2nd StreamBuilder in a SingleChildScrollView but I get this exception thrown:
I/flutter ( 2714): Another exception was thrown: RenderFlex children have non-zero flex but incoming height constraints are unbounded.
I/flutter ( 2714): Another exception was thrown: RenderBox was not laid out: RenderFlex#bafcc relayoutBoundary=up1 NEEDS-PAINT
I/flutter ( 2714): Another exception was thrown: RenderBox was not laid out: RenderFlex#bafcc relayoutBoundary=up1 NEEDS-PAINT
I managed to make the portion I wanted scrollable with ListView however this is using a FIXED container size with MediaQueryfor Patterns Completed. Is there any way to do so without fixing a height ? As you can see there is some extra unused space when I fix the height:
Expanded(
flex: 25,
child:StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection("Users")
.document(widget.userEmail)
.collection("Flight Data")
.document("Courses")
.collection(widget.courseAbbr)
.document(_dateSelected).snapshots(),
builder: (context, snapshot){
if (snapshot.hasError) {
return new Text('${snapshot.error}');
} else if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(white),));
} else {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(white),));
default:
var doc = snapshot.data;
// can execute animation here to make select flight glow
if (_dateSelected == null) {
return (Center(child: SizedBox(),));
}
return ListView(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height*2/3,
decoration: BoxDecoration(
color: blackTrans,
borderRadius: BorderRadius.all(Radius.circular(5.0)),
),
child: ListView.builder(
padding: EdgeInsets.only(top: 0.0),
scrollDirection: Axis.vertical,
shrinkWrap: false,
itemCount: _dateSelected == null ? 1 : doc["patterns"].length + 1,
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return Column(
children: <Widget>[
ListTile(
selected: false,
title: AutoSizeText(
NA_FLIGHT_PAGE_PATTERN,
maxLines: 1,
style: TextStyle(
color: white,
),
textAlign: TextAlign.center,
),
),
Divider(color: white,)
],
);
}
index -= 1;
var patterns = doc["patterns"];
return buildPatternTile(patterns[index]);
}
),
),
],
);
}
}
},
),
),
SingleChildScrollView use when you have a single box. I will suggest you to Use ListView
Here is the sample you can put your views directly
ListView(
padding: const EdgeInsets.all(8.0),
children: <Widget>[
Container(
height: 50,
color: Colors.amber[600],
child: const Center(child: Text('Entry A')),
),
Container(
height: 50,
color: Colors.amber[500],
child: const Center(child: Text('Entry B')),
),
Container(
height: 50,
color: Colors.amber[100],
child: const Center(child: Text('Entry C')),
),
],
)
This is the best way if you want to scroll the views between the multiple widgets