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);
}
},
)
],
);
}
}
Author by
JMain
Updated on December 09, 2022Comments
-
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); } }, ) ], ); } }