Dhaval Shah
Dhaval Shah

Reputation: 1028

Nested ListView.Builder fails while rendering - Flutter

I am trying a flutter app to build a small game I have a List<List<ColumnData>> gameBoard where ColumnData is a locally defined class. The list is supposed to be a 2-D list in triangle format e.g. for a level as 3 the list will look like:

[00]
[10][11]
[20][21][22]

My simulation code is:

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}
class ColumnData {
  int _rowIdx;
  int _colIdx;
  String _data;
  Widget _column;

  ColumnData({String data, int row, int col}) {
    _data = data;
    _rowIdx = row;
    _colIdx = col;
    _column = new InkWell(
      child: new Container(
        margin: new EdgeInsets.all(10.0),
        child: new SizedBox(width: 30.0, height: 30.0,
          child: new Text(_data, textAlign: TextAlign.center),
        ),
        decoration: new BoxDecoration(
          border: new Border.all(color: Colors.indigoAccent, width: 2.0),
          shape: BoxShape.rectangle,
        ),
      ),
    );
  }
  set data(String value) => _data=value;
  set colIdx(int value) => _colIdx=value;
  set rowIdx(int value) => _rowIdx=value;
  Widget get column => _column;
  String get data => _data;
  int get colIdx => _colIdx;
  int get rowIdx => _rowIdx;
  String toString(){
    return ('Row: ${_rowIdx} Col: ${_colIdx} Data: $_data');
  }
}

class _MyHomePageState extends State<MyHomePage> {
  List<List<ColumnData>> gameBoard = new List(3);

  @override
  void initState() {
    super.initState();
    print('Lets build matrix');
    initGame();
  }
  @override
  Widget build(BuildContext context) {
    return new Scaffold(body: _buildBody());
  }

  Widget _buildBody() {
    return  new ListView.builder(
      scrollDirection: Axis.horizontal,
      itemCount: gameBoard.length,
      shrinkWrap: true,
      itemBuilder: (BuildContext ctx, int rowIdx){
        return new ListView.builder(
          scrollDirection: Axis.vertical,
          shrinkWrap: true,
          itemCount: gameBoard[rowIdx].length,
          itemBuilder: (BuildContext ctx, int colIdx){
            return gameBoard[rowIdx][colIdx].column;
          },
        );
      },
    );
  }

  initGame(){
    List<ColumnData> row1Data = [];
    List<ColumnData> row2Data = [];
    List<ColumnData> row3Data = [];
    ColumnData colData00 = new ColumnData(row: 0, col: 0, data: '7');
    row1Data.add(colData00);
    gameBoard[0] =row1Data;
    ColumnData colData10 = new ColumnData(row: 1, col: 0, data: '5');
    ColumnData colData11 = new ColumnData(row: 1, col: 1, data: '2');
    row2Data.add(colData10);
    row2Data.add(colData11);
    gameBoard[1]= row2Data;
    ColumnData colData20 = new ColumnData(row: 2, col: 0, data: '3');
    ColumnData colData21 = new ColumnData(row: 2, col: 1, data: '2');
    ColumnData colData22 = new ColumnData(row: 2, col: 2, data: '0');
    row3Data.add(colData20);
    row3Data.add(colData21);
    row3Data.add(colData22);
    gameBoard[2] = row3Data;
    print(gameBoard);
  }

}

The snippet of the error that I am getting is:

I/flutter (21273): [[Row: 0 Col: 0 Data: 7], [Row: 1 Col: 0 Data: 5, Row: 1 Col: 1 Data: 2], [Row: 2 Col: 0 Data: 3, Row: 2 Col: 1 Data: 2, Row: 2 Col: 2 Data: 0]]
Restarted app in 9,341ms.
I/flutter (21273): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter (21273): The following assertion was thrown during performLayout():
I/flutter (21273): 'package:flutter/src/rendering/viewport.dart': Failed assertion: line 1283 pos 16:
I/flutter (21273): 'constraints.hasBoundedWidth': is not true. 
I/flutter (21273): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter (21273): more information in this error message to help you determine and fix the underlying cause.
I/flutter (21273): In either case, please report this assertion by filing a bug on GitHub: 
I/flutter (21273):   https://github.com/flutter/flutter/issues/new
I/flutter (21273): When the exception was thrown, this was the stack:
I/flutter (21273): #2      RenderShrinkWrappingViewport.performLayout (package:flutter/src/rendering/viewport.dart)
I/flutter (21273): #3      RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
......

I have tried without shrinkwrap and also by using rows and columns but no success

What exactly am I doing wrong? Any other variations that I can explore? Any other work-around/idea to build the 2-D matrix

Any pointers welcom

Regards

Upvotes: 1

Views: 2664

Answers (2)

Dhaval Shah
Dhaval Shah

Reputation: 1028

Since my game area had finite numbers of rows and columns (designed based on level selected and some other parameters), I was able to achieve my layout with following:

Widget _buildBody() {
    List<Widget> rows = new List<Widget>(gameBoard.length);
    for (int i=0;i<gameBoard.length; i++){
      List<Widget> cols = new List<Widget>(gameBoard[i].length);
      for(int j=0; j<gameBoard[i].length; j++){
        cols[j] = gameBoard[i][j].column;
      }
      rows[i] = new Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: cols,
      );
    }

    return new Container(
      child: new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: rows,
      ),
    );
}

After this the output is

enter image description here

Upvotes: 1

R&#233;mi Rousselet
R&#233;mi Rousselet

Reputation: 277047

ListView inside ListView is a bad idea to begin with.

This is a typical use-case for CustomScrollView. Where you can combine multiple scrollview into one single unit. And still have that smooth virtualized rendering where only what's visible is rendered on screen.

This tranform your build method into the following :

@override
Widget build(BuildContext context) {
  final slivers = new List<Widget>(gameBoard.length);

  for (var i = 0; i < gameBoard.length; i++) {
    slivers[i] = new SliverList(
      delegate: new SliverChildBuilderDelegate((context, j) {
        return gameBoard[i][j].column;
      }, childCount: gameBoard[i].length),
    );
  }
  return new Scaffold(
    body: new CustomScrollView(
      shrinkWrap: true,
      slivers: slivers,
    ),
  );
}

Upvotes: 1

Related Questions