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 () {}.
Related
I am struggling with getting the data from my 'Firebase'.
The 'print(mUsers)' are getting me the users from 'Firebase', so it is working up to that point. So I think my problem is with this: 'final users = UserModel.fromRTDB(Map<String, dynamic>.from(value));'
I see there is many ways to do this, but I have used this guide: https://www.youtube.com/watch?v=sXBJZD0fBa4&t=1649s
Here is my StreamBuilder:
StreamBuilder(
stream: _database
.child('brukere')
.limitToLast(10)
.onValue,
builder: (context, snapshot){
final tilesList = <ListTile>[];
if(snapshot.hasData) {
final mUsers = Map<String, dynamic>.from(
(snapshot.data! as DatabaseEvent).snapshot.value as Map );
tilesList.addAll(mUsers.values.map((value) {
print(mUsers);
final users =
UserModel.fromRTDB(Map<String, dynamic>.from(value));
return ListTile(
onTap: () {
print(users.name);
},
isThreeLine: true,
leading: Icon (Icons.person),
title: Text(users.name),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children:<Widget> [
my mistake.
My firebase children was not identical to my 'UserModel' class.
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)
);
}
),
I am currently working on the chat aspect of my app.
and I set up an AnimatedList inside of a StreamBuilder in order to make the messages appear in reverse.
This is my code
children: <Widget>[
new Flexible(
child: new StreamBuilder<QuerySnapshot> (
stream: chatRoomRef.document(widget.chatRoomID).collection('messages')
.orderBy('time').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
return new AnimatedList(
reverse: true,
padding: const EdgeInsets.all(8.0),
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
return new ChatMessageListItem(
context: context,
index: index,
animation: animation,
reference: snapshot,
);
}
);
},
),
),
My problem is that the builder is never hit, so the AnimatedList is never called. I am not sure the setup is correct so any help on this is much appreciated.
Edit:
I am trying to make it work like the FirebaseAnimatedList widget. I dont know if that helps with understanding my goal here.
Thank you
Try to validate if your snapshot has data
children: <Widget>[
new Flexible(
child: new StreamBuilder<QuerySnapshot> (
stream: chatRoomRef.document(widget.chatRoomID).collection('messages')
.orderBy('time').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
return snapshot.hasData ? new AnimatedList(
reverse: true,
padding: const EdgeInsets.all(8.0),
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
return new ChatMessageListItem(
context: context,
index: index,
animation: animation,
reference: snapshot,
);
}
): new CircularProgressIndicator();
},
),
),
Update:
I fixed it by adding a custom animation as well as modifying the code in the documentation of cloud_firestore.
My code looks like this now
new Flexible(
child: new StreamBuilder<QuerySnapshot> (
stream: chatRoomRef.document(widget.chatRoomID).collection('messages')
.orderBy('time').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
return snapshot.hasData ? new ListView(
physics: const AlwaysScrollableScrollPhysics(),
reverse: true,
padding: const EdgeInsets.all(8.0),
children: snapshot.data.documents.map((DocumentSnapshot messageSnapshot) {
return new ChatMessageListItem(
context: context,
animation: _animation,
messageSnapshot: messageSnapshot,
currentUserEmail: curUserEmail,
);
}).toList(),
): const CircularProgressIndicator();
I know this code only displays title and i want to make a onTap method to navigate to a new route, but this is how fare i made it, any help, hint, tip, even shaming me for how stupid i am would be very much appreciated.
Edit: I did posted the whole code because something is going wrong even after help that i got here. maybe is a syntax problem or maybe i am just too stupid
Widget build(BuildContext context) {
return new Scaffold(
body: new ListView.builder(
itemCount: data == null ? 0 : 10,
itemBuilder: (BuildContext context, int index){
return new Card(
child: new ListTile(
onTap: _onTapped,
title : new Text(data[index]["title"]),
),
);
},
),
);
}
}
Just wrap your title in a GestureDecector to handle clicks.
Then call Navigator's pushNamed to redirect to a new route.
new GestureDetector(
onTap: () {
Navigator.pushNamed(context, "myRoute");
},
child: new Text("my Title"),
);
An easier approach I found is to just wrap the item inside the ListTile with a FlatButton (or some interactive widget). In your code, for example:
Widget build(BuildContext context) {
return new Scaffold(
body: new ListView.builder(
itemCount: data == null ? 0 : 10,
itemBuilder: (BuildContext context, int index){
return new Card(
child: new ListTile(
title: FlatButton(
child: new Text(data[index]["title"]),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => YourPage()),
);
},
),
),
);
},
),
);
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(),