Flutter - Dynamic list with data coming from the database

Viewed 11123

I needed to create the DialogItem widgets using the data that comes from the database. I tried to use for(){} but it did not work.

Could you help me solve this problem?

I put the used code as well as the evidence that works, just does not work the dynamic list of DialogItem with the data of the database.

To work the code below, you need to insert the sqflite and path_provider dependencies into pubspec.yaml, thus:

dependencies:
  sqflite: any
  path_provider: any
  flutter:
    sdk: flutter

The DatabaseClient class will create the database with 3 records.

In the gif only foo1 appears the correct one would be to appear all the values of the list that came from the database:

[{name: foo1, color: 0}, {name: foo2, color: 1}, {name: foo3, color: 2}]

enter image description here

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  DatabaseClient _db = new DatabaseClient();
  int number;
  List listCategory;

  List colors = [
    const Color(0xFFFFA500),
    const Color(0xFF279605),
    const Color(0xFF005959)
  ];

  createdb() async {
    await _db.create().then(
      (data){
        _db.countCategory().then((list){
          this.number = list[0][0]['COUNT(*)']; //3
          this.listCategory = list[1];
          //[{name: foo1, color: 0}, {name: foo2, color: 1}, {name: foo3, color: 2}]
        });
      }
    );
  }

  @override
  void initState() {
    super.initState();
    createdb();    
  }

  void showCategoryDialog<T>({ BuildContext context, Widget child }) {
    showDialog<T>(
      context: context,
      child: child,
    )
    .then<Null>((T value) {
      if (value != null) {
        setState(() { print(value); });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(),
      body: new Center(
        child: new RaisedButton(
          onPressed: (){           
            showCategoryDialog<String>(
              context: context,
              child: new SimpleDialog(
                title: const Text('Categories'),
                children: <Widget>[
                  //for(var i = 0; i < this.number; i++) {
                    new DialogItem(
                      icon: Icons.brightness_1,
                      color: this.colors[
                        this.listCategory[0]['color']
                        //the zero should be dynamic going from 0 to 2 with the for(){}
                        //but o for(){} dont work
                      ],
                      text: this.listCategory[0]['name'],
                      onPressed: () {
                        Navigator.pop(context, this.listCategory[0]['name']);
                      }
                    ),
                  //}                  
                ]
              )
            );
          },
          child: new Text("ListButton"),
        )
      ),
    );
  }
}

//Creating Database with some data and two queries
class DatabaseClient {
  Database db;

  Future create() async {
    Directory path = await getApplicationDocumentsDirectory();
    String dbPath = join(path.path, "database.db");
    db = await openDatabase(dbPath, version: 1, onCreate: this._create);
  }

  Future _create(Database db, int version) async {
    await db.execute("""
            CREATE TABLE category (
              id INTEGER PRIMARY KEY,
              name TEXT NOT NULL,
              color INTEGER NOT NULL
            )""");
    await db.rawInsert("INSERT INTO category (name, color) VALUES ('foo1', 0)");
    await db.rawInsert("INSERT INTO category (name, color) VALUES ('foo2', 1)");
    await db.rawInsert("INSERT INTO category (name, color) VALUES ('foo3', 2)");
  }

  Future countCategory() async {
    Directory path = await getApplicationDocumentsDirectory();
    String dbPath = join(path.path, "database.db");
    Database db = await openDatabase(dbPath);

    var count = await db.rawQuery("SELECT COUNT(*) FROM category");
    List list = await db.rawQuery('SELECT name, color FROM category');
    await db.close();

    return [count, list];
  }
}

//Class of Dialog Item
class DialogItem extends StatelessWidget {
  DialogItem({ 
    Key key,
    this.icon,
    this.size,
    this.color,
    this.text,
    this.onPressed }) : super(key: key);

  final IconData icon;
  double size = 36.0;
  final Color color;
  final String text;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return new SimpleDialogOption(
      onPressed: onPressed,
      child: new Container(
        child: new Row(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            new Container(              
              child: new Container(
                margin: size == 16.0 ? new EdgeInsets.only(left: 7.0) : null,
                child: new Icon(icon, size: size, color: color),
              )                
            ),        
            new Padding(
              padding: size == 16.0 ?
                const EdgeInsets.only(left: 17.0) :
                const EdgeInsets.only(left: 16.0),
              child: new Text(text),
            ),
          ],
        ),
      )
    );
  }
}
2 Answers

There might be other issues, but as a start, I think this code

for(var i = 0; i < this.number; i++) {
  ...
}

should be changed to

children: this.number == null ? null :  
  new List(this.number).map((i) => 
    new DialogItem(
      icon: Icons.brightness_1,
      color: this.colors[
        this.listCategory[0]['color']
        //the zero should be dynamic going from 0 to 2 with the for(){}
        //but o for(){} dont work
      ],
      text: this.listCategory[0]['name'],
      onPressed: () {
        Navigator.pop(context, this.listCategory[0]['name']);
      }
    ).toList(),

to not throw an exception when this.number is null (response not yet received from the database).

and wrap the code that updates the state with setState(() {...})

  createdb() async {
    await _db.create().then(
      (data){
        _db.countCategory().then((list){
          setState(() {
            this.number = list[0][0]['COUNT(*)']; //3
            this.listCategory = list[1];
          //[{name: foo1, color: 0}, {name: foo2, color: 1}, {name: foo3, color: 2}]
          });
        });
      }
    );
  }

I found the solution, according to the Flutter - Build Widgets dynamically and Flutter - Combine dynamically generated elements with hard-coded ones and in this question

As SimpleDialog only accepts a List of type Widget - <Widget>[] I declare a variable tiles of type List<Widget> - List<Widget> tiles; and created a function of type List<Widget> - List<Widget> buildTile(int counter) {... - to be able to return a List<Widget>

Because of Navigator.pop (context, ... I needed to create the buildTile() function inside the Widget build(BuildContext context) {...

In the buildTile() function I added a for() to insert into the Widget type list as many DialogItem Widgets were needed, according to the result that comes from the database

and wrap the code that updates the state with setState(() {...}) as explained by Günter Zöchbauer

setState(() {
  this.number = list[0][0]['COUNT(*)']; //3
  this.listCategory = list[1];
  //[{name: foo1, color: 0}, {name: foo2, color: 1}, {name: foo3, color: 2}]
})

The complete code working as well as the demo are below:

To work the code below, you need to insert the sqflite and path_provider dependencies into pubspec.yaml, thus:

dependencies:
  sqflite: any
  path_provider: any
  flutter:
    sdk: flutter

enter image description here

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  DatabaseClient _db = new DatabaseClient();
  int number;
  List listCategory;
  List<Widget> tiles;

  List colors = [
    const Color(0xFFFFA500),
    const Color(0xFF279605),
    const Color(0xFF005959)
  ];

  createdb() async {
    await _db.create().then(
      (data){
        _db.countCategory().then((list){
          setState(() {
            this.number = list[0][0]['COUNT(*)']; //3
            this.listCategory = list[1];            
            //[{name: foo1, color: 0}, {name: foo2, color: 1}, {name: foo3, color: 2}]
          });          
        });
      }
    );
  }

  @override
  void initState() {
    super.initState();
    createdb();    
  }

  void showCategoryDialog<T>({ BuildContext context, Widget child }) {
    showDialog<T>(
      context: context,
      child: child,
    )
    .then<Null>((T value) {
      if (value != null) {
        setState(() { print(value); });
      }
    });
  }

  @override
  Widget build(BuildContext context) {

    List<Widget> buildTile(int counter) {
      this.tiles = [];
      for(var i = 0; i < counter; i++) {
        this.tiles.add(
          new DialogItem(
            icon: Icons.brightness_1,
            color: this.colors[
              this.listCategory[i]['color']
            ],
            text: this.listCategory[i]['name'],
            onPressed: () {
              Navigator.pop(context, this.listCategory[i]['name']);
            }
          )
        );
      }
      return this.tiles;
    }

    return new Scaffold(
      appBar: new AppBar(),
      body: new Center(
        child: new RaisedButton(
          onPressed: (){           
            showCategoryDialog<String>(
              context: context,
              child: new SimpleDialog(
                title: const Text('Categories'),
                children: buildTile(this.number)
              )
            );
          },
          child: new Text("ListButton"),
        )
      ),
    );
  }
}

//Creating Database with some data and two queries
class DatabaseClient {
  Database db;

  Future create() async {
    Directory path = await getApplicationDocumentsDirectory();
    String dbPath = join(path.path, "database.db");
    db = await openDatabase(dbPath, version: 1, onCreate: this._create);
  }

  Future _create(Database db, int version) async {
    await db.execute("""
            CREATE TABLE category (
              id INTEGER PRIMARY KEY,
              name TEXT NOT NULL,
              color INTEGER NOT NULL
            )""");
    await db.rawInsert("INSERT INTO category (name, color) VALUES ('foo1', 0)");
    await db.rawInsert("INSERT INTO category (name, color) VALUES ('foo2', 1)");
    await db.rawInsert("INSERT INTO category (name, color) VALUES ('foo3', 2)");
  }

  Future countCategory() async {
    Directory path = await getApplicationDocumentsDirectory();
    String dbPath = join(path.path, "database.db");
    Database db = await openDatabase(dbPath);

    var count = await db.rawQuery("SELECT COUNT(*) FROM category");
    List list = await db.rawQuery('SELECT name, color FROM category');
    await db.close();

    return [count, list];
  }
}

//Class of Dialog Item
class DialogItem extends StatelessWidget {
  DialogItem({ 
    Key key,
    this.icon,
    this.size,
    this.color,
    this.text,
    this.onPressed }) : super(key: key);

  final IconData icon;
  double size = 36.0;
  final Color color;
  final String text;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return new SimpleDialogOption(
      onPressed: onPressed,
      child: new Container(
        child: new Row(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            new Container(              
              child: new Container(
                margin: size == 16.0 ? new EdgeInsets.only(left: 7.0) : null,
                child: new Icon(icon, size: size, color: color),
              )                
            ),        
            new Padding(
              padding: size == 16.0 ?
                const EdgeInsets.only(left: 17.0) :
                const EdgeInsets.only(left: 16.0),
              child: new Text(text),
            ),
          ],
        ),
      )
    );
  }
}
Related