I'm learning flutter, and I'm trying to achieve a set of clickable cards, I successfully created the cards, however when I tried to use GestureDetector and wrap it up in a listview builder I get the following error
Vertical viewport was given unbounded height. Viewports expand in the
scrolling direction to fill their container.In this case, a vertical
viewport was given an unlimited amount of vertical space in which to
expand.
Please find the code below (task_card.dart):
import 'package:flutter/material.dart';
import 'product_detail.dart';
class TaskCard extends StatelessWidget {
final Map<String, dynamic> product;
final Function updateProduct;
final Function deleteProduct;
final int productIndex;
TaskCard(this.product, this.productIndex, this.updateProduct, this.deleteProduct);
#override
Widget build(BuildContext context) {
return ListView.builder(
//shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) {
return ProductDetail(
product: product[index],
productIndex: index,
updateProduct: updateProduct,
deleteProduct: deleteProduct,
);
}));
},
child: Card(
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 10.0, top: 5.0),
child: Text(
product['title'],
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
),
);
}
);
}
}
(task.dart)
import 'package:flutter/material.dart';
import 'task_card.dart';
class Tasks extends StatelessWidget {
final List<Map<String, dynamic>> products;
final Function updateProduct;
final Function deleteProduct;
Tasks(this.products, this.updateProduct, this.deleteProduct);
Widget _buildTaskCard() {
Widget taskCard = Center(
child: Text('No Products found'),
);
if (tasks.length > 0) {
taskCard = ListView.builder(
itemBuilder: (BuildContext context, int index) =>
TaskCard(products[index], index, updateProduct, deleteProduct),
itemCount: products.length,
);
}
return taskCard;
}
#override
Widget build(BuildContext context) {
return _buildTaskCard();
}
}
I've tried warping up my listview builder in a flexible widget and also using shrink wrap but non of them worked (shrink wrap crashed the application).
I'm trying to make the card clickable so that it navigates to another page.
any help is appreciated, thanks :)
Okay, so I guess I found a fix. I have added an unnecessary listview builder in the task_card.dart and called it through tasks.dart which already has a listview builder. Sharing my code for anyone who want to refer.
task.dart
import 'package:flutter/material.dart';
import 'task_card.dart';
class Tasks extends StatelessWidget {
final List<Map<String, dynamic>> products;
final Function updateProduct;
final Function deleteProduct;
Tasks(this.products, this.updateProduct, this.deleteProduct);
Widget _buildTaskCard() {
Widget taskCard = Center(
child: Text('No Products found'),
);
if (tasks.length > 0) {
taskCard = ListView.builder(
itemBuilder: (BuildContext context, int index) =>
TaskCard(products[index], index, updateProduct, deleteProduct),
itemCount: products.length,
);
}
return taskCard;
}
#override
Widget build(BuildContext context) {
return _buildTaskCard();
}
}
task_card.dart
import 'package:flutter/material.dart';
import 'product_detail.dart';
class TaskCard extends StatelessWidget {
final Map<String, dynamic> product;
final Function updateProduct;
final Function deleteProduct;
final int productIndex;
TaskCard(this.product, this.productIndex, this.updateProduct, this.deleteProduct);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) {
return ProductDetail(
product: product,
productIndex: productIndex,
updateProduct: updateProduct,
deleteProduct: deleteProduct,
);
}));
},
child: Card(
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 10.0, top: 5.0),
child: Text(
product['title'],
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
),
);
}
}
Related
I need list view builder to generate tiles based on the number of documents there will be in firebase for now I am just trying to sort the UI. I dont understand why its breaking. Image 1 is when the ListView.buidler is commented out. Image 2 is leaving ListView in.
List item
import 'package:flutter/material.dart';
import 'package:track/src/widgets/admin_navbar.dart' as widgets;
import 'package:track/src/widgets/colour_icon_button.dart' as widgets;
import 'package:track/src/features/clients/domain/client_firebase_storage.dart';
class ClientsPage extends StatefulWidget {
const ClientsPage({Key? key}) : super(key: key);
#override
State<ClientsPage> createState() => _ClientsPageState();
}
class _ClientsPageState extends State<ClientsPage> {
late final ClientFirebaseStorage _clientsService;
late double screenWidth;
late double screenHeight;
#override
void initState() {
_clientsService = ClientFirebaseStorage();
super.initState();
}
#override
Widget build(BuildContext context) {
screenWidth = MediaQuery.of(context).size.width;
screenHeight = MediaQuery.of(context).size.height;
return Scaffold(
appBar: AppBar(
title: const FlutterLogo(),
),
drawer: const widgets.AdminNavBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Clients',
style: Theme.of(context).textTheme.headline1,
),
const SizedBox(
width: 30,
),
const widgets.ColourIconButton(icon: Icon(Icons.search_rounded)),
const SizedBox(
width: 5,
),
const widgets.ColourIconButton(
icon: Icon(Icons.swap_vert_rounded),
),
SizedBox(
width: screenWidth - 350,
),
const widgets.ColourIconButton(
icon: Icon(Icons.add),
),
],
),
SizedBox(
height: 190,
),
Text('Test1'),
Text('Test2'),
Text('Test3'),
ListView.builder(
itemBuilder: (context, index) {
return ListTile(
onTap: () {},
title: Text('#'),
);
},
)
// StreamBuilder(
// stream: _clientsService.allClients(),
// builder: (context, snapshot) {
// switch (snapshot.connectionState) {
// case ConnectionState.waiting:
// case ConnectionState.active: //implicit fall through
// if (snapshot.hasData) {
// final allClients = snapshot.data as Iterable<Client>;
// return ClientsListView(
// clients: allClients,
// onTap: (clients) {},
// );
// } else {
// return const CircularProgressIndicator();
// }
// default:
// return const CircularProgressIndicator();
// }
// },
// ),
],
),
);
}
}
Before adding List.viewbuilder
After adding list.viewbuilder
for the first picture (before adding Listview.builder) items are rendered in center because you have a Row inside your Column, Column & Row have a default CrossAxisAlignment.center
After adding the ListView.builder, the log will be showing you an error, ListView here needs to be either inside an Expanded or shrinkWrap: true,
Setting an Expanded as a parent for the ListView will make the listview scrollable only, but adding the attribute shrinkWrap: true will stop the scrolling feature in your Listview, and then you will have to put your Column inside a Listview or SingleChildScrollView
i am new to flutter and been trying to create a function that refresh the ListView.builder based on users choice.i am saving cities names as Strings inside my firestore documents in user collection.
i have multiple buttons that presents different cities and based on choice i need the ListView builder to rebuild. i have been struggling for a while trying to find the solution to this.
anyone here can help?
this is how i retrieve data from firestore
StreamBuilder(
stream: Firestore.instance.collection('users').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text('loading...');
return Container(
width: 890.0,
height: 320.0,
margin: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 00.0),
child: new ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int index) {
User user = User.fromDoc(snapshot.data
.documents[index]);
return Padding(
padding: const EdgeInsets.only(top: 0),
child: Container(
height: 300,
width: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(0),
),
child: _buildCard(user)),
);
}),
);
},
),
I just wrote this code to show the implementation for static no of cities, clicking the buttons changes the index which then changes the texts(you will change them to stream builders with custom city streams), you can also scale it to dynamic list by manipulating the city list.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key,}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int stackIndex = 0;
final List<String> cities = ['Berlin', 'Denver', 'Nairobi', 'Tokyo', 'Rio'];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample'),
),
body: Center(
child: Column(
mainAxisAlignment : MainAxisAlignment.spaceEvenly,
children : [
Row(
mainAxisAlignment : MainAxisAlignment.spaceEvenly,
mainAxisSize : MainAxisSize.max,
children : cities.map((city){
return RaisedButton(
child : Text(city),
onPressed : (){
setState((){
this.stackIndex = cities.indexOf(city);
});
}
);
}).toList()
),
IndexedStack(
index : stackIndex,
children: cities.map((city){
return yourStreamBuilder(city);
}).toList()
),
])
),
);
}
Widget yourStreamBuilder(String city){
//you can use your custom stream here
//Stream stream = Firestore.instance.collection('users').where('myCity', isEqualTo: city).snapshots();
return Text(city);//replace this with your streamBuilder
}
}
int stackIndex = 0;
final List<String> cities =[
'Stockholm',
'Malmö',
'Uppsala',
'Västerås',
'Örebro',
'Linköping',
'Helsingborg',
'Jönköping',
'Norrköping',
'Lund',
'Umeå',
'Gävle',
'Södertälje',
'Borås',
'Huddinge',
'Eskilstuna',
'Nacka',
'Halmstad',
'Sundsvall',
'Södertälje',
'Växjö',
'Karlstad',
'Haninge',
'Kristianstad',
'Kungsbacka',
'Solna',
'Järfälla',
'Sollentuna',
'Skellefteå',
'Kalmar',
'Varberg',
'Östersund',
'Trollhättan',
'Uddevalla',
'Nyköping',
'Skövde',
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment : MainAxisAlignment.spaceEvenly,
children: <Widget>[
Row(
mainAxisAlignment : MainAxisAlignment.spaceEvenly,
mainAxisSize : MainAxisSize.max,
children: cities.map((city) {
return OutlineButton(
child: Text(city),
onPressed: (){
setState(() {
this.stackIndex = cities.indexOf(city);
});
},
);
}).toList()
),
IndexedStack(
index: stackIndex,
children: cities.map((city){
return myStreamBuilder(city);
})
)
],
),
),
);
}
Widget myStreamBuilder(String city){
Stream stream = Firestore.instance.collection('users').where('myCity', isEqualTo: city).snapshots();
return Text(city);
}
}
I need to make a dynamic ListView height inside another ListView. None of the answers here I have come to didn't really answer it. I've made a simple example of what I'm trying to do so you can simply copy and paste it to try it and play with it. I've got problem with sub ListView where I need to make it grow or shrink based on number of items in it (problem commented in program)
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
List<List<bool>> subList = [
[true, true],
[true]
];
class _TestState extends State<Test> {
#override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (BuildContext context, int index) {
return Column(
children: <Widget>[
ListTile(
trailing: IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
subList[index].add(true);
});
},
),
title: Text(
'item $index',
style: TextStyle(fontWeight: FontWeight.w600),
),
),
Container(
height: 100, // <--- this needs to be dynamic
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int subIndex) {
return TestRow(
text: 'sub$subIndex',
onRemove: () {
setState(() {
subList[index].removeAt(subIndex);
});
});
},
itemCount: subList[index].length,
),
)
],
);
},
separatorBuilder: (BuildContext context, int index) => Divider(),
itemCount: subList.length);
}
}
class TestRow extends StatelessWidget {
final String text;
final Function onRemove;
const TestRow({this.onRemove, this.text});
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(text),
IconButton(
icon: Icon(Icons.remove),
onPressed: onRemove,
)
],
),
);
}
}
BTW I managed to make a workaround by changing height of container (commented part) to height: 50.0 * subList[index].length where 50 is height of sub title. I'm still looking for a proper way of doing it where I wouldn't need to hardcode height of the tile and calculate it
Here is video of the project with workaround how it should work
Try setting the shrinkWrap property to true and remove the container
ListView.builder(
shrinkWrap: true, //<-- Here
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int subIndex) {
return TestRow(
text: 'sub$subIndex',
onRemove: () {
setState(() {
subList[index].removeAt(subIndex);
});
});
},
itemCount: subList[index].length,
)
Output:
I am making an app with different build methods so that I can make a list of items to save them on another screen when the "love heart" button is tapped. But I am getting errors in the code. I am following the Flutter Codelabs app tutorial part 2.
My code:
import 'package:aioapp2/lists.dart';
import 'package:flutter/material.dart';
class _FavoriteListState extends State<FavoriteList> {
final _suggestions = [];
final Set<Widget> _saved = Set<Widget>();
Widget _buildList() {
return ListView.builder(
itemCount: 53,
itemBuilder: (BuildContext context, int index) {
return _buildRow(_suggestions[index]);
},
);
}
Widget _buildRow(Widget website){
final bool alreadySaved = _saved.contains(website);
return Card(
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
subtitle: Row(
children: <Widget>[
// Image.asset('lib/images/${images[index]}'),
],
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _buildList(),
);
}
}
class FavoriteList extends StatefulWidget {
#override
_FavoriteListState createState() => _FavoriteListState();
}
The error that I'm facing is in the Image.asset() line. On typing the following line its showing red line under the "index". But it shouldn't and that's the problem! Any help?
You are attempting to reference the variable index from within the _buildRow method, but that variable doesn't exist there. Take a look at this excerpt from your code
Widget _buildList() {
return ListView.builder(
itemCount: 53,
itemBuilder: (BuildContext context, int index) { // <-- index declared here
return _buildRow(_suggestions[index]);
},
);
}
Widget _buildRow(Widget website){
final bool alreadySaved = _saved.contains(website);
return Card(
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
subtitle: Row(
children: <Widget>[
// Image.asset('lib/images/${images[index]}'), // <-- index has not been declared here
],
),
),
),
);
}
This is an example of scope. The variable index is defined in the builder method within _buildList. That means the variable only exists there. You can't access it outside that method, so when you try to access it within _buildRow, you get an error.
If you want to pass the value of index to the _buildRow, you need to pass it as an argument to the _buildRow method:
Widget _buildList() {
return ListView.builder(
itemCount: 53,
itemBuilder: (BuildContext context, int index) { // <-- index declared here
return _buildRow(_suggestions[index], index); // Passing it as an argument
},
);
}
Widget _buildRow(Widget website, int index) {
final bool alreadySaved = _saved.contains(website);
return Card(
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
subtitle: Row(
children: <Widget>[
Image.asset('lib/images/${images[index]}'), // <-- index has been declared in the parameter list so everything is ok here
],
),
),
),
);
}
(Your code doesn't declare images anywhere either, but I'm assuming you declare it elsewhere in code that you didn't share since you aren't reporting the error there.)
Try like this a simple workaround:
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: FavoriteList(),
)
);
class _FavoriteListState extends State<FavoriteList> {
final _suggestions = [{
'image' : 'ic_play.png'
},
{
'image' : 'ic_play.png'
},
{
'image' : 'ic_play.png'
},
{
'image' : 'ic_play.png'
}
];
final Set<Widget> _saved = Set<Widget>();
Widget _buildList() {
return ListView.builder(
itemCount: _suggestions.length,
itemBuilder: (BuildContext context, int index) {
return _buildRow(_suggestions[index]);
},
);
}
Widget _buildRow(dynamic website){
return Card(
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
subtitle: Row(
children: <Widget>[
Image.asset('assets/images/${website['image']}'),
],
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _buildList(),
);
}
}
class FavoriteList extends StatefulWidget {
#override
_FavoriteListState createState() => _FavoriteListState();
}
Shouldnt
Image.asset('lib/images/${images[index]}'),
be like
Image.asset('lib/images/${index}.png'), // Provide you have 0.png, 1.png .... in lib folder
Or you should had an String array names images like
String[] images=["1.png","2.png"];
=====
I cant see any variable images which you want to reference in the commented code.
I need to display images of several different sizes in a ListView.
When the image is larger than screen.width, I'd like it to shrink to fit width.
But when the image is shorter, I'd like it to keep its original size.
How can I do it? Thanks in advance.
I tried putting Image inside Flex, but couldn't "stop" the small one to expand.
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:firebase_database/firebase_database.dart';
void main() => runApp(MyApp());
const _imagesDir = "images";
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Image List',
theme: ThemeData(primarySwatch: Colors.blue,),
home: MyListPage(title: 'Image List Page'),
);
}
}
class MyListPage extends StatefulWidget {
MyListPage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyListPageState createState() => _MyListPageState();
}
class _MyListPageState extends State<MyListPage> {
Widget build1(BuildContext context, AsyncSnapshot snapshot) {
Widget _tileImagem(BuildContext context, String imageName) {
imageName = _imagesDir + "/"+ imageName;
return Padding(padding:EdgeInsets.all(2.0),
child: Flex(
direction: Axis.vertical,
children: <Widget>[
Image.asset(imageName),
]
),
);
}
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(40.0),
child: AppBar(
title: Row(
children: <Widget> [
Padding(padding: EdgeInsets.only(right: 20.0),),
Text( 'Duda'),
]),
)
),
body: ListView(
shrinkWrap: true,
children: <Widget>[
Container(),
_tileImagem(context, 'flutter_big_medium.png'),
Container(), //My App have some different widgets
Container(),
Container(), //I kept them here just as place holder
Container(),
Container(),
Container(),
Container(),
Container(),
Divider(),
TileTexts(),
Divider(),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () { },
child: Icon(Icons.skip_next),
),
);
}
#override
Widget build(BuildContext context) {
return new FutureBuilder(
future:
FirebaseDatabase.instance.reference()
.child('Testing')
.once(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState){
case ConnectionState.done: return build1(context, snapshot);
case ConnectionState.waiting: return CircularProgressIndicator();
default:
if (snapshot.hasError) {
return Text("hasError: ${snapshot.error}");
} else {
return Text("${snapshot.data}");
}
}
}
);
}
}
class TileTexts extends StatefulWidget {
TileTexts() : super();
#override
_TileTextsState createState() => _TileTextsState();
}
class _TileTextsState extends State<TileTexts> {
#override
void initState() {
super.initState();
}
Widget text1(String title, String imageName, TextStyle style) {
return Expanded(
child:Container(
margin: const EdgeInsets.only(left: 10.0),
child: Column(
children: <Widget>[
Html(data: title,
useRichText: true,
defaultTextStyle: style,
),
((imageName == null))
? Container()
: Image.asset(_imagesDir + "/"+imageName),
]
),
),
);
}
Widget _tileDetail(BuildContext context, String imageName) {
return Container(
padding: EdgeInsets.fromLTRB(5.0,0.0,10.0,0.0),
child: Row(
children: <Widget>[
Material(
shape: RoundedRectangleBorder(borderRadius:BorderRadius.circular(22.0) ),
clipBehavior: Clip.antiAlias,
child: MaterialButton(
child: Text('X'),
color: Theme.of(context).accentColor,
elevation: 8.0,
height: 36.0,
minWidth: 36.0,
onPressed: () {
//
},
),
),
text1('<body>veja a imagem</body>', imageName, Theme.of(context).textTheme.caption),
],
),
);
}
//_TileTexts
#override
Widget build(BuildContext context) {
print('_TileTexts build');
return Column(
children: <Widget>[
_tileDetail(context, 'flutter_med_medium.png'),
Divider(),
_tileDetail(context, 'flutter_med_medium.png'),
Divider(),
_tileDetail(context, 'flutter_med_medium.png'),
],
);
}
}
Create an method,getTitleImage(imageName), that returns Flex if image is bigger then screen-with, else return the image inside an container or in other widget of choice.
....
return Padding(padding:EdgeInsets.all(2.0),
child: getTitleImage(imageName)
),
);
....
Here is some other tips and tricks using Flex
Please check the doc, it says:
The heights of the leading and trailing widgets are constrained according to the Material spec. An exception is made for one-line ListTiles for accessibility. Please see the example below to see how to adhere to both Material spec and accessibility requirements.
after reading docs, you should achieve what you want :)