Is it possible to loop over flutter cards? - dart

Here is what I am trying to do
child: FutureBuilder(
future: ProductRepo().getMyProduct(),
builder: (BuildContext context, AsyncSnapshot res){
if(res.data==null){
return Container(
child: Text('this is nice'),
);
}
return Container(
child: Column(
children: <Widget>[
Card(
child: Text('I just want to loop over this card :)'),
),
],
)
);
}
),
I always find people looping over listView.builder. Anyone can help, please. Thank you.

I think you are making a bit of confusion about the use of these widgets.
Indeed both ListView and Column can be used to display a list of widgets, BUT, ListView.builder(...) provides a way to reuse the widgets thus is more memory efficient when you have to create a large number of widgets.
For example, when you want to display a list of electronics for an e-commerce app. For each electronic item you have a photo, title & price, in this case you would want to use a ListView.builder, because the list can be huge and you don't want to run out of memory.
Now on the other hand, the Column should be used when you have a small number of widgets that should be displayed in a list-like way (or one beneath the other).
For your case, if you want to transform the list of objects that you have, into a list of cards you can do something like this:
FutureBuilder(
future: ProductRepo().getMyProduct(),
builder: (BuildContext context, AsyncSnapshot res){
if(res.data==null){
return Container(
child: Text('this is nice'),
);
}
return Container(
child: Column(
children: res.data.map((item) {
return Card(child: Text(item.name));
}).toList());
);
}
),
I've assumed that res.data is a list of elements and each element has a property called name. Also in the return Card(...) line you can do extra processing of the item if you need to do so.
Hope that this can help you :).
If you have to do more processing
You can extract the processing in a method or a chain of methods something like this:
List<Widget>prepareCardWidgets(List<SomeObject> theObjects){
//here you can do any processing you need as long as you return a list of ```Widget```.
List<Widget> widgets = [];
theObjects.forEach((item) {
widgets.add(Card(child: Text(item.name),));
});
return widgets;
}
Then you can use it like this:
FutureBuilder(
future: ProductRepo().getMyProduct(),
builder: (BuildContext context, AsyncSnapshot res){
if(res.data==null){
return Container(
child: Text('this is nice'),
);
}
return Container(
child: Column(
children: prepareCardWidgets(res.data)
);
}
),

Related

How to use well formatted arrow functions in Dart?

Working on a flutter/dart project I'm currently almost always thinking about a way to decrease my code size, considering that one of the things would be using arrow functions to avoid the brackets.
However I can't find a way to keep them in a nice look, e.g. if I use this code:
#widget
Widget poscompExams() => StoreConnector<AppState, ViewModel>(
converter: ViewModel.fromStore,
builder: (BuildContext context, ViewModel vm) => Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: vm.poscomp.exams.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Test'),
subtitle: const Text('Inserção das provas em andamento'),
leading: const Icon(Icons.computer),
onTap: () => {}
);
},
),
),
],
);
},
);
It would be much nicer if the look was like this:
#widget
Widget poscompExams() =>
StoreConnector<AppState, ViewModel>(
converter: ViewModel.fromStore,
builder: (BuildContext context, ViewModel vm) =>
Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: vm.poscomp.exams.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Test'),
subtitle: const Text('Inserção das provas em andamento'),
leading: const Icon(Icons.computer),
onTap: () => {}
);
},
),
),
],
);
},
);
I research about some way and find the dart_style, but it seems to follow the general same pattern to format.
It would be nice something like prettier on Javascript, with flag options.
Generally, if you get past six or seven levels of indentation, it's time to refactor. This will make it easier for you to override parts of it for variations, and easier for people reading and maintaining your code to understand your intent.
In your specific code, I'd take the ListView.builder out as a separate method in your class. There are IDE operations to help with that kind of refactor.
Also, in your code, () => {} is a function returning an empty map. You should fix that to just () {}.

list length from StreamBuilder?

In the header of my flutter app, I want to display a Chip with the number of items in the list displayed in the body. The build method of the body is using a StreamBuilder to create the list.
The problem is that the length of the list isn't known until after the AppBar is built and the StreamBuilder finishes building the list.
Since I can't call setState() from within the build function, how can I get the value in the AppBar to update after the StreamBuilder is finished building the list?
I'm working through this example. Here's the code snippet:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Streams'),
elevation: 1.0,
),
body: Container(
child: _buildContent(),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: _createCounter,
),
);
}
Widget _buildContent() {
return StreamBuilder<List<Counter>>(
stream: stream,
builder: (context, snapshot) {
return ListItemsBuilder<Counter>(
items: snapshot.hasData ? snapshot.data : null,
itemBuilder: (context, counter) {
return CounterListTile(
key: Key('counter-${counter.id}'),
counter: counter,
onDecrement: _decrement,
onIncrement: _increment,
onDismissed: _delete,
);
},
);
},
);
}
I've found a way around it by creating a second StreamBuilder just to update the Chip in the AppBar. Is it a good practice to have multiple StreamBuilders watching the same stream?

ListView does not refresh whereas attached list does (Flutter)

I'm trying to get familiar with flutter and I'm facing some weird case. I want to build a dynamic ListView where a + button allows to add elements. I wrote the following State code:
class MyWidgetListState extends State<MyWidgetList> {
List<Widget> _objectList = <Widget>[
new Text('test'),
new Text('test')
];
void _addOne() {
setState(() {
_objectList.add(new Text('test'));
});
}
void _removeOne() {
setState(() {
_objectList.removeLast();
});
}
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new ListView(
shrinkWrap: true,
children: _objectList
),
new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new IconButton(
icon: new Icon(Icons.remove_circle),
iconSize: 36.0,
tooltip: 'Remove',
onPressed: _objectList.length > 2 ? _removeOne : null,
),
new IconButton(
icon: new Icon(Icons.add_circle),
iconSize: 36.0,
tooltip: 'Add',
onPressed: _addOne,
)
],
),
new Text(_objectList.length.toString())
],
);
}
}
My problem here is that the ListView is visually stuck with the 2 elements I initialized it with.
Internally the _objectList is well managed. For testing purpose I added a simple Text widget at the bottom that shows the size of the list. This one works fine when I click the Add/Remove buttons and it gets properly refreshed. Am I missing something?
Flutter is based around immutable data. Meaning that if the reference to an object didn't change, the content didn't either.
The problem is, in your case you always send to ListView the same array, and instead mutate its content. But this leads to ListView assuming the list didn't change and therefore prevent useless render.
You can change your setState to keep that in mind :
setState(() {
_objectList = List.from(_objectList)
..add(Text("foo"));
});
Another Solution!!
Replace ListView with ListView.builder
Code:
ListView.builder(
itemBuilder: (ctx, item) {
return _objectList[item];
},
shrinkWrap: true,
itemCount: _objectList.length,
),
Output:

Flutter Trying to fire a showModalBottomSheet mystery

I question myself sometimes, whether I'm dumb, or Dart (Flutter) is weird.
How does this not work?
I'm using https://github.com/apptreesoftware/flutter_google_map_view
I show a map, and have added markers.
Since the package supports listeners, when a marker is tapped, I want to show a modal.
Does the listener work? Yep, because the print statement happens.
Does the modal work? I don't know. No error shows, nothing!
mapView.onTouchAnnotation.listen((annotation) {
print(annotation);
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 260.0,
child: Text('Text'),
);
},
);
});
Please, what is the magic bullet?
Edit
Lemme thrown in more flesh. This is my Scaffold widget.
MapView mapView = new MapView();
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
key: scaffoldKey,
appBar: new AppBar(
title: new Text('Map View Example'),
),
body: new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
showMap(context),
],
),
),
);
And showMap(...) looks like this:
showMap(context) {
mapView.show(
new MapOptions(
mapViewType: MapViewType.normal,
showUserLocation: true,
showMyLocationButton: true,
showCompassButton: true,
initialCameraPosition:
new CameraPosition(new Location(5.6404963, -0.2285315), 15.0),
hideToolbar: false,
title: "Dashboard"),
// toolbarActions: [new ToolbarAction("Close", 1)],
);
mapView.onMapReady.listen((_) {
mapView.setMarkers(_markers);
});
mapView.onTouchAnnotation.listen((annotation) {
print(annotation);
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 260.0,
child: Text('Text'),
);
},
);
});
}
The reason you've having issues is that your context doesn't contain a scaffold. If you look what you're doing in your code, your context actually comes from the widget enclosing your scaffold.
YourWidget <------------ context
MaterialApp
Scaffold
AppBar
Column
showMap....
There are a couple of ways to get around this. You can use a Builder widget something like this:
body: new WidgetBuilder(
builder: (context) => new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
showMap(context),
],
)
),
in which case the context is actually rooted below the scaffold.
YourWidget
MaterialApp
Scaffold
AppBar
Builder <------------ context
Column
showMap....
However, what I would actually recommend is breaking your class into multiple classes. If your build function gets large enough you have to separate it out into another function (that's only used once), there's a good chance you need a new widget!
You could either make a body widget (probably Stateless), or a widget just for showing the map (Stateless or Stateful depending on your needs)... or more likely both!
Now as to why you're not seeing any errors... are you running in debug or release mode (if you're in debug mode there should be a little banner in the top right of the screen)? If you're in release mode it might ignore the fact that there is no scaffold in the context and fail silently, whereas in debug mode it should throw an assertion error. Running from the IDE or with flutter run generally runs in debug mode, but you may have changed it.

Flutter - The screen is not scrolling

I inserted 6 cards, however it is not possible to scroll the screen.
According to the image below, a red stripe appears in the footer, and the screen does not scroll.
What is missing to be able to scroll the screen?
main.dart
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Myapp",
home: new HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) => new Scaffold(
appBar: new AppBar(
backgroundColor: new Color(0xFF26C6DA),
),
body: new Column(
children: <Widget>[
new Card(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const ListTile(
leading: const Icon(Icons.album),
title: const Text('The Enchanted Nightingale'),
subtitle: const Text('Music by Julie Gable. Lyrics by Sidney Stein.'),
),
],
),
),
...
...
...
],
)
);
}
Columns don't scroll. Try replacing your outer Column with a ListView. You may need to put shrinkWrap: true on it.
To make a column scrollable, simply wrap it in a SingleChildScrollView.
This might do the trick, worked like charm for me:
shrinkWrap: true, physics: ClampingScrollPhysics(),
I needed placing SingleChildScrollView inside Column. The SingleChildScrollView also needed Column as a child, but the scroll wasn't working for me in that case. The solution was to wrap the SingleChildScrollView with Expanded. So here's how it looked:
Column(
children: <Widget>[
MyFirstWidget(),
Expanded(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
// Scrollable content.
],
),
),
),
],
),
you have to put it on ListView.builder
ListView.builder(
itemBuilder: (BuildContext context, int index){
final item = yourItemLists[index];
new Card(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const ListTile(
leading: const Icon(Icons.album),
title: const Text('The Enchanted Nightingale'),
subtitle: const Text('Music by Julie Gable. Lyrics by Sidney Stein.'),
),
],
),
);
},
itemCount: yourItemLists.length,
);
A column in a column make the layout impossible to calculate without setting height.
The second column is useless since it contains only one element, try to put the ListTile directly as the body of the Card.
You should use ListView.builder in place of the inner column (as I suggested above that columns are not scrollable).
Set shrinkWrap: true, and physics: ClampingScrollPhysics() inside ListView.builder. Just using shrinkWrap: true didn't solve my problem. But setting physics to ClampingScrollPhysics() started to make it scroll.
There are generally two ways to make a screen scrollable. One is by using Column, and the other is by using ListView.
Use Column (wrapped in SingleChildScrollView):
SingleChildScrollView( // Don't forget this.
child: Column(
children: [
Text('First'),
//... other children
Text('Last'),
],
),
)
Use ListView:
ListView(
children: [
Text('First'),
//... other children
Text('Last'),
],
)
This approach is simpler to use, but you lose features like crossAxisAlignment. For this case, you can wrap your children widget inside Align and set alignment property.
You can also Wrap the parent Column in a Container and then wrap the Container with the SingleChildscrollView widget. (This solved my issue).
Just Add physics: ClampingScrollPhysics(),

Resources