Flutter populate ExpansionTile data (a Future object) on demand only, instead of on init

1,400

You can use Completer instead of Future, but still with FutureBuilder, here's the sample code

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

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

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

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

class _MyHomePageState extends State<MyHomePage> {
  Future<http.Response> _responseFuture;

  @override
  void initState() {
    super.initState();
    _responseFuture = http.get('https://jsonplaceholder.typicode.com/users');
    print("Getting Main List");
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('ExpansionTile Test'),
      ),
      body: new FutureBuilder(
        future: _responseFuture,
        builder: (BuildContext context, AsyncSnapshot<http.Response> response) {
          if (!response.hasData) {
            return const Center(
              child: const Text('Loading...'),
            );
          } else if (response.data.statusCode != 200) {
            return const Center(
              child: const Text('Error loading data'),
            );
          } else {
            List<dynamic> jsonn = json.decode(response.data.body);
            return new MyExpansionTileList(jsonn);
          }
        },
      ),
    );
  }
}

class MyExpansionTileList extends StatelessWidget {
  final List<dynamic> elementList;

  MyExpansionTileList(this.elementList);

  List<Widget> _getChildren() {
    List<Widget> children = [];
    elementList.forEach((element) {
      children.add(
        new MyExpansionTile(element['id'], element['name']),
      );
    });
    return children;
  }

  @override
  Widget build(BuildContext context) {
    return new ListView(
      children: _getChildren(),
    );
  }
}

class MyExpansionTile extends StatefulWidget {
  final int id;
  final String title;
  MyExpansionTile(this.id, this.title);
  @override
  State createState() => new MyExpansionTileState();
}

class MyExpansionTileState extends State<MyExpansionTile> {
  PageStorageKey _key;
  Completer<http.Response> _responseCompleter = new Completer();

  @override
  Widget build(BuildContext context) {
    _key = new PageStorageKey('${widget.id}');
    return new ExpansionTile(
      key: _key,
      title: new Text(widget.title),
      onExpansionChanged: (bool isExpanding) {
        if (!_responseCompleter.isCompleted) {
          _responseCompleter.complete(http.get('https://jsonplaceholder.typicode.com/users'));
           print("Getting Expansion Item # ${widget.id}");
        }
      },
      children: <Widget>[
        new FutureBuilder(
          future: _responseCompleter.future,
          builder:
              (BuildContext context, AsyncSnapshot<http.Response> response) {
            if (!response.hasData) {
              return const Center(
                child: const Text('Loading...'),
              );
            } else if (response.data.statusCode != 200) {
              return const Center(
                child: const Text('Error loading data'),
              );
            } else {
              List<dynamic> json_data = json.decode(response.data.body);
              List<Widget> reasonList = [];
              json_data.forEach((element) {
                reasonList.add(new ListTile(
                  dense: true,
                  title: new Text(element['email']),
                ));
              });
              return new Column(children: reasonList);
            }
          },
        )
      ],
    );
  }
}
Share:
1,400
JMain
Author by

JMain

Updated on December 09, 2022

Comments

  • JMain
    JMain over 1 year

    I'm able to populate a ListView and ExpansionTiles from REST/JSON, and that's fine, but I want the expansiontiles to NOT be initially populated, and to only be populated individually as they are clicked on (probably using onExpansionChanged:)

    I have the following, and it almost works like I want, except it is populating the ListView, and then after that it is immediately (automatically) populating the expansion tiles because of the FutureBuilder inside each ExpansionTile.

    How can I change this so that the ExpansionTiles are not populated until they are individually clicked/expanded?

    import 'dart:async';
    import 'package:flutter/material.dart';
    import 'package:http/http.dart' as http;
    import 'dart:convert';
    
    void main() {
      runApp(new MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'ExpansionTile Test',
          home: new MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      @override
      _MyHomePageState createState() => new _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      Future<http.Response> _responseFuture;
    
      @override
      void initState() {
        super.initState();
        _responseFuture = http.get('https://jsonplaceholder.typicode.com/users');
        print("Getting Main List");
      }
    
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          appBar: new AppBar(
            title: new Text('ExpansionTile Test'),
          ),
          body: new FutureBuilder(
            future: _responseFuture,
            builder: (BuildContext context, AsyncSnapshot<http.Response> response) {
              if (!response.hasData) {
                return const Center(
                  child: const Text('Loading...'),
                );
              } else if (response.data.statusCode != 200) {
                return const Center(
                  child: const Text('Error loading data'),
                );
              } else {
                List<dynamic> jsonn = json.decode(response.data.body);
                return new MyExpansionTileList(jsonn);
              }
            },
          ),
        );
      }
    }
    
    class MyExpansionTileList extends StatelessWidget {
      final List<dynamic> elementList;
    
      MyExpansionTileList(this.elementList);
    
      List<Widget> _getChildren() {
        List<Widget> children = [];
        elementList.forEach((element) {
          children.add(
            new MyExpansionTile(element['id'], element['name']),
          );
        });
        return children;
      }
    
      @override
      Widget build(BuildContext context) {
        return new ListView(
          children: _getChildren(),
        );
      }
    }
    
    class MyExpansionTile extends StatefulWidget {
      final int id;
      final String title;
      MyExpansionTile(this.id, this.title);
      @override
      State createState() => new MyExpansionTileState();
    }
    
    class MyExpansionTileState extends State<MyExpansionTile> {
      PageStorageKey _key;
      Future<http.Response> _responseFuture;
    
      @override
      void initState() {
        super.initState();
        _responseFuture = http.get('https://jsonplaceholder.typicode.com/users');
        print("Getting Expansion Item # ${widget.id}");
      }
    
      @override
      Widget build(BuildContext context) {
        _key = new PageStorageKey('${widget.id}');
        return new ExpansionTile(
          key: _key,
          title: new Text(widget.title),
          children: <Widget>[
            new FutureBuilder(
              future: _responseFuture,
              builder:
                  (BuildContext context, AsyncSnapshot<http.Response> response) {
                if (!response.hasData) {
                  return const Center(
                    child: const Text('Loading...'),
                  );
                } else if (response.data.statusCode != 200) {
                  return const Center(
                    child: const Text('Error loading data'),
                  );
                } else {
                  List<dynamic> json_data = json.decode(response.data.body);
                  List<Widget> reasonList = [];
                  json_data.forEach((element) {
                    reasonList.add(new ListTile(
                      dense: true,
                      title: new Text(element['email']),
                    ));
                  });
                  return new Column(children: reasonList);
                }
              },
            )
          ],
        );
      }
    }