Fellps
Fellps

Reputation: 415

Why I can't use childrens of ListView on body of a scaffold widget?

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

Answers (3)

Shubhamhackz
Shubhamhackz

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

Alexander Farkas
Alexander Farkas

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

fartem
fartem

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

Related Questions