flutterdart

How to make widget working faster - custom json convert widget


I have a list where I want to display a list of objects. Each object has a json field where "tags" are defined. Each consists of a field name and a value. The values from the fields contained in json have to be displayed in a nice looking form of "tiles". When a field is focused, the field name (ToolTip) should appears.

I wrote my widget that converts json for this. Works fine, but I've found that performance of list slows down much with number of items (scrolling and hovering over an item) At 10 it is still ok, but at 50 it's not. When I comment "JsonWidget" and just use Text widget, everything works fast. I wonder if there is a way to write it in a more optimal way.

Her is full code:

import 'dart:convert';
import 'package:flutter/material.dart';

void main() {
  runApp(GenistryApp());
}

//My example object with json field
class MyObject {
  int id;
  String json =
      '{"year":1850,"1 name":"Zuzanna","names":"Stefania","surname":"Zabłocka","sex":"W","city":"Warsaw","date":"1850.01.02","father":"Piotr","mothers_anme":"Józefa","mothers_surname":"Wojnicz","info":"Szlachetni"}';
  MyObject(this.id);
}

class GenistryApp extends StatelessWidget {
  const GenistryApp({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: MyTableWidget(),
        theme: ThemeData(
          primarySwatch: Colors.lightGreen,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ));
  }
}

class MyTableWidget extends StatefulWidget {
  const MyTableWidget({Key key}) : super(key: key);

  @override
  _MyTableWidgetState createState() => _MyTableWidgetState();
}

class _MyTableWidgetState extends State<MyTableWidget> {
  MyDataTableSource _data = MyDataTableSource([]);

  List<MyObject> list = [];

  //Generating 50 same elements for testing
  getData() {
    for (int i = 0; i < 50; i++) {
      list.add(MyObject(i));
    }
    _data = MyDataTableSource(list);
  }

  @override
  Widget build(BuildContext context) {
    getData();
    return Scaffold(
        backgroundColor: Colors.white.withOpacity(0.8),
        body: SingleChildScrollView(
          scrollDirection: Axis.vertical,
          child: PaginatedDataTable(
            source: _data,
            columns: [
              DataColumn(label: Container()),
              DataColumn(
                  label: ConstrainedBox(
                      constraints: BoxConstraints.expand(
                          width: MediaQuery.of(context).size.width))),
            ],
            columnSpacing: 50,
            horizontalMargin: 10,
            rowsPerPage: 50,
            showCheckboxColumn: false,
          ),
        ));
  }
}

class MyDataTableSource extends DataTableSource {
  List<MyObject> _data;
  MyDataTableSource(this._data);

  bool get isRowCountApproximate => false;
  int get rowCount => _data.length;
  int get selectedRowCount => 0;
  DataRow getRow(int index) {
    return DataRow(cells: [
      DataCell(Text("ID")),
      DataCell(
        JsonWidget(_data[index]
            .json), //  Here is my widget to convert json field - it slows all list
        // Text(_data[index].json), //  Here is simple json field for testing - it works fast
        onTap: () => {},
      ),
    ]);
  }
}

//Widget to convert json to nice looking "tags"
class JsonWidget extends StatelessWidget {
  String jdata;
  Map<String, dynamic> data;
  JsonWidget(String jdata) {
    this.jdata = jdata;
  }
  var containers = <Tooltip>[];
  @override
  Widget build(BuildContext context) {
    this.data = json.decode(this.jdata);
    data.entries.forEach((element) {
      containers.add(new Tooltip(
          message: element.key, //tag's fieldname
          textStyle: TextStyle(color: Colors.white),
          child: Container(
            margin: const EdgeInsets.all(3),
            padding: const EdgeInsets.all(4),
            child: Text(element.value.toString()), //tag's fieldvalue
            decoration: BoxDecoration(
                border: Border.all(color: Colors.blueAccent, width: 0.7),
                borderRadius: BorderRadius.all(Radius.circular(20))),
          )));
    });
    return Row(children: this.containers);
  }
}


Solution

  • The build method is designed in such a way that it should be pure/without side effects. It should only return a widget and not run for loops as in the JsonWidget case. A better way to write this will be using listview builder.

      class JsonWidget extends StatelessWidget {
      Map<String, dynamic> data;
      JsonWidget(Map<String, dynamic> data) {
        this.data = data;
      }
      var containers = <Tooltip>[];
      @override
      Widget build(BuildContext context) {
        return ListView.builder(
            shrinkWrap: true,
            scrollDirection: Axis.horizontal,
            itemCount: data.entries.length,
            itemBuilder: (context, index) {
              return Tooltip(
                  message: data.entries.toList()[index].key, //tag's fieldname
                  textStyle: TextStyle(color: Colors.white),
                  child: Container(
                    margin: const EdgeInsets.all(3),
                    padding: const EdgeInsets.all(4),
                    child: Text(data.entries
                        .toList()[index]
                        .value
                        .toString()), //tag's fieldvalue
                    decoration: BoxDecoration(
                        border: Border.all(color: Colors.blueAccent, width: 0.7),
                        borderRadius: BorderRadius.all(Radius.circular(20))),
                  ));
            });
      }
    }
    

    And MyDataTableSoure will look like this

    class MyDataTableSource extends DataTableSource {
      List<MyObject> _data;
      MyDataTableSource(this._data);
    
      bool get isRowCountApproximate => false;
      int get rowCount => _data.length;
      int get selectedRowCount => 0;
      DataRow getRow(int index) {
        return DataRow(cells: [
          DataCell(Text("ID")),
          DataCell(
            JsonWidget(json.decode(_data[index]
                .json)), //  Here is my widget to convert json field - it slows all list
            // Text(_data[index].json), //  Here is simple json field for testing - it works fast
            onTap: () => {},
          ),
        ]);
      }
    }