Reputation: 415
I'm studing Flutter so I decided to create a Todo list app. First of all: my focus is a Windows app, that could be the problem tougth.
I'm tryng to do two lists, one with the to do items e other with done items. So I build a Scaffold Widget with a body using a Column with two lists:
import 'package:flutter/material.dart';
void main() => runApp(new TodoApp());
class TodoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Todo List',
home: new TodoList(),
);
}
}
class TodoList extends StatefulWidget {
@override
createState() => new TodoListState();
}
class TodoListState extends State<TodoList> {
List<String> _todoItems = [];
List<String> _doneItems = [];
Widget _buildTodoList() {
return new ListView.builder(
itemCount: _todoItems.length,
itemBuilder: (BuildContext context, int index) {
return new ListTile(
title: new Text(_todoItems[index]),
onTap: () => _promptRemoveTodoItem(index));
},
);
}
Widget _buildDoneList() {
return new ListView.builder(
itemCount: _doneItems.length,
itemBuilder: (BuildContext context, int index) {
return new ListTile(
title: new Text(_doneItems[index]),
onTap: () => _promptRemoveDoneItem(index));
},
);
}
void _promptRemoveTodoItem(int index) {
showDialog(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
title: new Text('Mark "${_todoItems[index]}" as done?'),
actions: <Widget>[
new TextButton(
child: new Text('Cancel'),
onPressed: () => Navigator.of(context).pop(),
),
new TextButton(
onPressed: () {
_removeTodoItem(index);
Navigator.of(context).pop();
},
child: new Text('Mark as done'),
)
]);
});
}
void _removeTodoItem(int index) {
setState(() {
_doneItems.add(_todoItems.removeAt(index));
});
}
void _promptRemoveDoneItem(int index) {
showDialog(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
title: new Text('Redo "${_doneItems[index]}" ?'),
actions: <Widget>[
new TextButton(
child: new Text('Cancel'),
onPressed: () => Navigator.of(context).pop(),
),
new TextButton(
onPressed: () {
_removeDoneItem(index);
Navigator.of(context).pop();
},
child: new Text('Redo task'),
)
]);
});
}
void _removeDoneItem(int index) {
setState(() {
_todoItems.add(_doneItems.removeAt(index));
});
}
void _pushAddTodoScreen() {
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Add a new task')),
body: new TextField(
autofocus: true,
onSubmitted: (val) {
_addTodoItem(val);
Navigator.pop(context);
},
decoration: new InputDecoration(
hintText: 'Enter something to do...',
contentPadding: const EdgeInsets.all(16.0)),
));
}));
}
void _addTodoItem(String task) {
if (task.length > 0) {
setState(() => _todoItems.add(task));
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Todo List')),
body: Column(children: <Widget>[
_buildTodoList(),
_buildDoneList(),
]),
floatingActionButton: new FloatingActionButton(
onPressed: _pushAddTodoScreen,
tooltip: 'Add task',
child: new Icon(Icons.add)),
);
}
}
but when I run flutter run -d windows
I got a strange error:
[ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: Cannot hit test a render box that has never been laid out.
The hitTest() method was called on this RenderBox: RenderStack#f0d97 NEEDS-LAYOUT NEEDS-PAINT:
needs compositing
creator: Stack ← _FloatingActionButtonTransition ← MediaQuery ← LayoutId-[<_ScaffoldSlot.floatingActionButton>] ← CustomMultiChildLayout ← AnimatedBuilder ← DefaultTextStyle ← AnimatedDefaultTextStyle ← _InkFeatures-[GlobalKey#584bd ink renderer] ← NotificationListener<LayoutChangedNotification> ← PhysicalModel ← AnimatedPhysicalModel ← ⋯
parentData: offset=Offset(0.0, 0.0); id=_ScaffoldSlot.floatingActionButton
constraints: MISSING
size: MISSING
alignment: Alignment.centerRight
textDirection: ltr
fit: loose
Unfortunately, this object's geometry is not known at this time, probably because it has never been laid out. This means it cannot be accurately hit-tested.
If you are trying to perform a hit test during the layout phase itself, make sure you only hit test nodes that have completed layout (e.g. the node's children, after their layout() method has been called).
#0 RenderBox.hitTest.<anonymous closure> (package:flutter/src/rendering/box.dart:2391:11)
#1 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2422:6)
#2 RenderBoxContainerDefaultsMixin.defaultHitTestChildren.<anonymous closure> (package:flutter/src/rendering/box.dart:2785:25)
#3 BoxHitTestResult.addWithPaintOffset (package:flutter/src/rendering/box.dart:787:31)
#4 RenderBoxContainerDefaultsMixin.defaultHitTestChildren (package:flutter/src/rendering/box.dart:2780:33)
#5 RenderCustomMultiChildLayoutBox.hitTestChildren (package:flutter/src/rendering/custom_layout.dart:417:12)
#6 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#7 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#8 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#9 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#10 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#11 RenderPhysicalModel.hitTest (package:flutter/src/rendering/proxy_box.dart:1892:18)
#12 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#13 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#14 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#15 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#16 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#17 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#18 RenderIgnorePointer.hitTest (package:flutter/src/rendering/proxy_box.dart:3249:31)
#19 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#20 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#21 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#22 RenderFractionalTranslation.hitTestChildren.<anonymous closure> (package:flutter/src/rendering/proxy_box.dart:2706:22)
#23 BoxHitTestResult.addWithPaintOffset (package:flutter/src/rendering/box.dart:787:31)
#24 RenderFractionalTranslation.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:2700:19)
#25 RenderFractionalTranslation.hitTest (package:flutter/src/rendering/proxy_box.dart:2686:12)
#26 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#27 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#28 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#29 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#30 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#31 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#32 RenderOffstage.hitTest (package:flutter/src/rendering/proxy_box.dart:3368:31)
#33 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#34 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#35 _RenderTheatre.hitTestChildren.<anonymous closure> (package:flutter/src/widgets/overlay.dart:765:25)
#36 BoxHitTestResult.addWithPaintOffset (package:flutter/src/rendering/box.dart:787:31)
#37 _RenderTheatre.hitTestChildren (package:flutter/src/widgets/overlay.dart:760:33)
#38 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#39 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#40 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#41 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#42 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#43 RenderAbsorbPointer.hitTest (package:flutter/src/rendering/proxy_box.dart:3466:17)
#44 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#45 RenderProxyBoxWithHitTestBehavior.hitTest (package:flutter/src/rendering/proxy_box.dart:180:19)
#46 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#47 RenderCustomPaint.hitTestChildren (package:flutter/src/rendering/custom_paint.dart:536:18)
#48 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#49 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#50 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#51 RenderProxyBoxMixin.hitTestChildren (package:flutter/src/rendering/proxy_box.dart:133:19)
#52 RenderBox.hitTest (package:flutter/src/rendering/box.dart:2424:11)
#53 RenderView.hitTest (package:flutter/src/rendering/view.dart:173:14)
#54 RendererBinding.hitTest (package:flutter/src/rendering/binding.dart:481:16)
#55 GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:288:7)
#56 GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:280:5)
#57 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:238:7)
#58 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:221:7)
#59 _rootRunUnary (dart:async/zone.dart:1370:13)
#60 _CustomZone.runUnary (dart:async/zone.dart:1265:19)
#61 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1170:7)
#62 _invoke1 (dart:ui/hooks.dart:180:10)
#63 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:276:7)
#64 _dispatchPointerDataPacket (dart:ui/hooks.dart:96:31)
however if I use only one builder list without column on the scaffold's body, like this:
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Todo List')),
body: _buildTodoList(),
floatingActionButton: new FloatingActionButton(
onPressed: _pushAddTodoScreen,
tooltip: 'Add task',
child: new Icon(Icons.add)),
);
}
it works pretty well!
What's going on?
Upvotes: 0
Views: 313
Reputation: 7973
As already answered you can use one ListView
. But in case you if you want to use two separate ListView
scrolling independently then you can use this approach :
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: _buildTodoList()
),
Expanded(
child: _buildDoneList(),,
),
],
),
),
);
}
Both ListView
will take half the space of the screen, you can adjust this according to your need.
Upvotes: 1
Reputation: 698
You should wrap your lists with Expanded. Lists are unconstrained by default, so they cannot be safely placed in flex widgets.
Also, you can omit "new" in Dart 2.0
Upvotes: 1
Reputation: 2541
Try to use one ListView
. You can extract all TODOs and sort it in order that you want. For it, you need to add to your entity parameter isDone
(or similar). For example:
class ToDo {
final String text;
bool isDone;
ToDo(this.text, this.isDone);
}
ListView.builder(
itemCount: _todos.length,
itemBuilder: (BuildContext context, int index) {
final todo = _allItems[index];
// Here check TODO is done or not
if (todo.isDone) {
return ListTile(
title: new Text(todo.text),
onTap: () => _handleCompletedToDo(index),
);
}
retur ListTile(
title: new Text(todo.text),
onTap: () => _handleActiveToDo(index),
);
},
);
You also can find how to work with ListView
and why wrap it to Column
not working here.
Upvotes: 1