How to create a Expansion Tile from List<List<dynamic>> in Flutter?

630 views
Skip to first unread message

Niyazi Toros

unread,
Apr 14, 2019, 8:20:24 AM4/14/19
to Flutter Dev



I have List> array and I need to create a Expansion Tiles.

My Expansion Tile must look like below format.


final List<Entry> data = <Entry>[


 
new Entry('Varlıklar',

   
<Entry>[

     
new Entry('Vadeli Hesaplar',

       
<Entry>[

         
new Entry('TRY'),

         
new Entry('USD'),

         
new Entry('EUR'),

         
new Entry('GBP'),

         
new Entry('TL TOPLAM'),

       
],

     
),

     
new Entry('Vadesiz Hesaplar',

       
<Entry>[

         
new Entry('TRY'),

         
new Entry('USD'),

         
new Entry('EUR'),

         
new Entry('GBP'),

         
new Entry('TL TOPLAM'),

       
],

     
),

     
new Entry('Toplam',

       
<Entry>[

         
new Entry('TRY'),

         
new Entry('USD'),

         
new Entry('EUR'),

         
new Entry('GBP'),

         
new Entry('TL TOPLAM'),

       
],

     
),

   
],

 
),

 
new Entry('Borçlar',

   
<Entry>[

     
new Entry('Vadeli Hesaplar',

       
<Entry>[

         
new Entry('TRY'),

         
new Entry('USD'),

         
new Entry('EUR'),

         
new Entry('GBP'),

         
new Entry('TL TOPLAM'),

       
],

     
),

     
new Entry('Vadesiz Hesaplar',

       
<Entry>[

         
new Entry('TRY'),

         
new Entry('USD'),

         
new Entry('EUR'),

         
new Entry('GBP'),

         
new Entry('TL TOPLAM'),

       
],

     
),

     
new Entry('Toplam',

       
<Entry>[

         
new Entry('TRY'),

         
new Entry('USD'),

         
new Entry('EUR'),

         
new Entry('GBP'),

         
new Entry('TL TOPLAM'),

       
],

     
),

   
],

 
),




];



The data that comes from server is look like as shown below. I am taking first array value and assign to above format as an example;

  • 2 means Vadeli Hesaplar,
  • 142980 next to "TRY"
  • 20042 next to "USD"
  • 65912 next to "EUR"
  • 0 next to "GBP" and
  • 758201 next to "TL TOPLAM"

So my question is, How to create a Expansion Tile from List> in Flutter?



[

   
[

       
2,

       
142980,

       
20042,

       
65912,

       
0,

       
758201

   
],

   
[

       
1,

       
2404,

       
0,

       
0,

       
0,

       
2404

   
],

   
[

       
9,

       
145384,

       
20042,

       
65912,

       
0,

       
760605

   
]

 

]


Steven McDowall

unread,
Apr 14, 2019, 9:24:19 AM4/14/19
to Niyazi Toros, Flutter Dev

The key is to always make sure your data structure is appropriate for the job at hand ... In our case we put a lot of logic into the model for "Resource" .. then a little routine to create the widgets from a valid Resource Tree.. the details aren't too important .. except the concept of "parent" and "children" .. which causes the nesting within the ResourceTile or ListTile (parent and children respectively) .. 

Once you have a good data model -- the rest flows pretty easily .. but the model is key (IMHO) 


---- Resource.dart

// Model for a resource (Link / Link - Directory)
import 'package:logging/logging.dart';

class Resource {
  Resource(this.linkId, this.linkDirectoryId, this.displayName,
      [this.authorNameFirst,
      this.authorNameLast,
      this.createdTime,
      this.linkUrl,
      this.mimeType,
      this.storedFilename]);

  int linkId;
  int linkDirectoryId;
  String displayName;
  String authorNameLast;
  String authorNameFirst;
  String createdTime;
  String linkUrl;
  String mimeType;
  String storedFilename;

  static Resource createResourceFromLink(dynamic link) {
    final Resource _itemResource = Resource(
      link['linkId'],
      link['linkDirectroyId'],
      link['displayName'],
      link['authorNameFirst'],
      link['authorNameLast'],
      link['createdTime'],
      link['linkUrl'],
      link['mimeType'],
      link['storedFilename'],
    );

    return _itemResource;
  }
}

// Define what will be our array (or arrays of arrays, etc) of resources..
class ResourceEntry {
  ResourceEntry(this.directoryId, this.resource,
      {this.children, this.isFolder = false});

  final int directoryId;
  final Resource resource;
  bool isFolder;
  List<ResourceEntry> children;
}

// OK -- now create the utility class to hold everything ..
class ResourceTree {
  ResourceTree() {
    resourceTree = <ResourceEntry>[];
  }

  final Logger logger = Logger('ResourceTree');

  List<ResourceEntry> resourceTree;

  void clear() => resourceTree.clear();

  int length() => resourceTree.length;

  ResourceEntry get(int pos) => resourceTree[pos];

  ResourceEntry operator [](int pos) => resourceTree[pos];

  final List<int> _directoryIdsAdded = <int>[];

  bool isEmpty() {
    return resourceTree.isEmpty;
  }

  bool isNotEmpty() {
    return resourceTree.isNotEmpty;
  }

  // For the Customer Recource Library page - need to parse all teh data and fill the tree
  void addResourcesFromCustomerResourceQuery(dynamic data) {
    final List<dynamic> _resourceList =
        data['customerByCustomerId']['customerLinksByCustomerId']['nodes'];
    final List<dynamic> _directoryList = data['customerByCustomerId']
        ['customerLinkDirectoriesByCustomerId']['nodes'];
    if (_resourceList.isNotEmpty) {
      // OK -- build our node strucutre first...
      _addResourcesFromLists(_resourceList, _directoryList);
    }
  }

  // For the Event Detail Recources page - need to parse all teh data and fill the tree
  void addResourcesFromEventResourceQuery(dynamic data) {
    // Get the bare resources for the event...
    final List<dynamic> _resourceList =
        data['eventByEventId']['eventLinksByEventId']['nodes'];
    final List<dynamic> _directoryList =
        data['eventByEventId']['eventLinkDirectoriesByEventId']['nodes'];

    // logger.finer('** DIRECTORY LIST FROM EVENT **');
    // logger.finer(_directoryList);

    // Get the bare resources for the customer that is in all events..
    _resourceList.addAll(data['eventByEventId']['customerByCustomerId']
        ['customerLinksByCustomerId']['nodes']);
    _directoryList.addAll(data['eventByEventId']['customerByCustomerId']
        ['customerLinkDirectoriesByCustomerId']['nodes']);

    // logger.finer('** DIRECTORY LIST FROM CUSTOMER **');
    // logger.finer(_directoryList);

    // See if this was generated from a Template (Event Category) and if so see if there
    // is anything here to grab
    if (data['eventByEventId']['eventCategoryByEventCategoryId'] != null) {
      // Get the bare resources for the Event Category Level (Templates)...
      // These have a sub-element so they are a bit harder...
      final List<dynamic> _templateLinks = data['eventByEventId']
                  ['eventCategoryByEventCategoryId']
              ['eventCatCustomerLinkMapsByEventCategoryId']['nodes']
          .map((dynamic link) => link['customerLinkByCustomerLinkId'])
          .toList();
      // logger.finer('**** DATA ****');
      // logger.finer(_templateLinks);
      _resourceList.addAll(_templateLinks);

      // Grab the directories from the template..
      final List<dynamic> _templateDirs = data['eventByEventId']
                  ['eventCategoryByEventCategoryId']
              ['eventCatCustomerLinkDirMapsByEventCategoryId']['nodes']
          .map((dynamic dir) =>
              dir['customerLinkDirectoryByCustomerLinkDirectoryId'])
          .toList();
      // logger.finer('** DIRECTORY LIST FROM TEMPLATE **');
      // logger.finer(_templateDirs);
      _directoryList.addAll(_templateDirs);
    }

    // logger.finer('** RESOURCE LIST **');
    // logger.finer(_resourceList);
    // logger.finer('** DIRECTORY LIST **');
    // logger.finer(_directoryList);

    // See if we have anything to show after all that work!
    if (_resourceList.isNotEmpty) {
      // OK -- build our node strucutre first...
      _addResourcesFromLists(_resourceList, _directoryList);
    }
  }

  // Main routine that will fill in the tree from two lists -- resources and directories..
  void _addResourcesFromLists(
      List<dynamic> _resourceList, List<dynamic> _directoryList) {
    // keep list of directories and resource so we do not add duplicates.
    _directoryIdsAdded.clear();

    // Walk through the root level resources ...
    _addResourcesToNode(null, _resourceList);

    // Now add the root folder structures and then the resources for each folder
    // logger.fine('directory list');
    // logger.fine(_directoryList);

    _addFolders(null, _resourceList, _directoryList);
  }

  // Given a folder (somewhere) grab the children and recurse if needed ..
  void _addFolders(ResourceEntry folder, List<dynamic> _resourceList,
      List<dynamic> _directoryList) {
    // Any resources at this level?
    final int directoryId = folder?.directoryId;

    // logger.fine('[_addSubFolders() - subfolders for $directoryId');
    // Unless we're at the root level (null) add our entry to the tree..
    if (folder != null) {
      _addResourcesToNode(folder, _resourceList);
    }

    // Now look for subfolders and recurse back to us ..
    _directoryList
        .where(
            (dynamic folder) => folder['parentLinkDirectoryId'] == directoryId)
        // ignore: avoid_function_literals_in_foreach_calls
        .forEach((dynamic directoryEntry) {
      // Create our simple ResourceEntry for the folder..
      final Resource _folderResource = _createFolderResource(directoryEntry);
      final ResourceEntry _entry = ResourceEntry(
          _folderResource.linkDirectoryId, _folderResource,
          children: <ResourceEntry>[], isFolder: true);

      // Add this to our root or the folder if it's alive..
      // logger.fine('Directory id: ${_entry.directoryId}');
      if (!_directoryIdsAdded.contains(_entry.directoryId)) {
        _directoryIdsAdded.add(_entry.directoryId);
        if (folder == null) {
          // logger.fine(
          //     'Adding directory id: ${_entry.directoryId} to resourceTree');
          resourceTree.add(_entry);
        } else {
          // logger.fine(
          //     'Adding directory id: ${_entry.directoryId} to directory ${folder.directoryId}');
          folder.children.add(_entry);
        }
      }
      // Now recurse for children folders
      _addFolders(_entry, _resourceList, _directoryList);
    });
  }

  Resource _createFolderResource(dynamic folder) {
    // logger.fine('creating resource for folder:');
    // logger.fine(folder);
    final Resource _folderResource =
        Resource(0, folder['linkDirectoryId'], folder['name']);
    return _folderResource;
  }

  void _addResourcesToNode(
      ResourceEntry nodeRoot, List<dynamic> _resourceList) {
    // Find any resources for this node and add them ..
    final int directoryId = nodeRoot?.directoryId;

    // logger.fine(
    //     '[ResourceLibraryScreen - _addResourcesToNode()] - searching for directroyId $directoryId');
    // Now walk the resources and find the ones that should be here and add it to the children
    _resourceList
        .where((dynamic link) => link['linkDirectoryId'] == directoryId)
        // ignore: avoid_function_literals_in_foreach_calls
        .forEach((dynamic link) {
      // logger.fine('[_addResourcesToNode() - adding resource $link to folder');
      // logger.fine(link);
      final Resource _item = Resource.createResourceFromLink(link);
      final ResourceEntry _entry = ResourceEntry(0, _item);

      // Add it to the proper locale .. either the root or the child node..
      // logger.fine('Resource Link id: ${_entry.resource.linkId}');

      if (nodeRoot == null) {
        // logger.fine(
        //     'Adding resource Link id: ${_entry.resource.linkId} to resourceTree');
        resourceTree.add(_entry);
      } else {
        // logger.fine(
        //     'Adding resource Link id: ${_entry.resource.linkId} to nodeRoot ${nodeRoot.directoryId}');
        nodeRoot.children.add(_entry);
      }
    });
  }
}


-- ResourceTile.dart

// Show and control one resource tile

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

import '../../models/resource.dart';
import '../../util/mime_utils.dart';
import '../utilities/show_resource.dart';

import './expansionTile.dart';

final Logger _logger = Logger('ResourceTile');

class ResourceTile extends StatelessWidget {
  const ResourceTile({
    Key key,
    this.resourceEntry,
  }) : super(key: key);

  final ResourceEntry resourceEntry;

  @override
  Widget build(BuildContext context) {
    _logger.fine('[build()]');

    final Resource _resource = resourceEntry.resource;
    Widget _returnTile;

    // If we're a folder use the ExpansionTile..
    if (resourceEntry.isFolder) {
      // ThemeData _theme = Theme.of(context);
      // _logger.fine('Theme divider color is ${_theme.dividerColor}');
      _returnTile = Theme(
        data: Theme.of(context).copyWith(
          dividerColor: Colors.yellow,
        ),
        child: MMExpansionTile(
          key: PageStorageKey<ResourceEntry>(resourceEntry),
          title: Text(
            resourceEntry.resource.displayName,
          ),
          children: resourceEntry.children
              .map<Widget>((ResourceEntry resourceEntry) =>
                  ResourceTile(resourceEntry: resourceEntry))
              .toList(),
        ),
      );
    } else {
      // We're a normal thing.. find an icon and just use the Listtile..
      IconData _iconData;
      if (_resource.mimeType == null) {
        // Must be a URL
        _iconData = Icons.bookmark;
      } else {
        _iconData = getIconForMime(_resource.mimeType);
      }
      _returnTile = ListTile(
        leading: Icon(
          _iconData,
        ),
        title: Text(
          resourceEntry.resource.displayName,
        ),
        onTap: () => Launch.handleResourceTap(context,
            url: resourceEntry.resource.linkUrl,
            storedFilename: resourceEntry.resource.storedFilename,
            mimeType: resourceEntry.resource.mimeType),
      );
    }
    // Ok -- this is a parent node.. use an Expansion Tile

    return _returnTile;
  }
}


--
You received this message because you are subscribed to the Google Groups "Flutter Dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to flutter-dev...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Niyazi Toros

unread,
Apr 15, 2019, 12:48:07 AM4/15/19
to Flutter Dev
Thanks Steven,

First of all I will consider your warning. 
Secondly, this is my first time to trying to use Expansion Tile. I will try to create a test project and try to use your code for studying.

Kind Regards,
Reply all
Reply to author
Forward
0 new messages