Looking for some guidance on building expansionTile list dynamically. I have a successful Listview built dynamically from json API, but can not find any examples on building the expansionTile. I have 1 api call that brings back the top level and another call for each top level to bring back the expansion list. Anyone have an example of this? I have found the static example but is not clear how to how to make it dynamic.
Here is some code I came up with. I can see the Tile title portion and can see the json come in for the tile body, but can not figure out how to get the correct name of the list title in the body, nothing I try to set it to works. Any ideas?
import 'dart:async';
import 'package:intl/intl.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
//import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
//import 'package:cswauthapp/models.dart';
import 'package:flutter/foundation.dart';
import 'dart:convert';
var jsonCodec = const JsonCodec();
List<Exp> myReasonList;
List myDCList;
int mycount = 0;
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> {
@override
void initState() {
super.initState();
_getData();
//_getSpecialty();
}
_getData() async {
var _url = 'http://$baseurl:8080/support/dc/1';
var http = createHttpClient();
var response = await http.get(_url);
var dc = await jsonCodec.decode(response.body);
myDCList = await dc.toList();
print('DC: '+myDCList.toString());
if (mounted) {
setState(() {
//_dataReceived = true;
mycount = myDCList.length;
});
}
}
Future _getChildren(int did) async {
var _url2 = 'http://174.138.61.246:8080/support/dcreasons/$did';
var http = createHttpClient();
var response = await http.get(_url2);
var reasons = await jsonCodec.decode(response.body);
myReasonList = await reasons.toList();
print('REASONS: '+ myReasonList.toString());
return myReasonList;
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('ExpansionTile Test'),
),
body: new ListView.builder(
itemBuilder: _itemBuilder,
itemCount: mycount,
),
);
}
Widget _itemBuilder(BuildContext context, int index) {
Exp exp = getExp(index);
return new ListChild(exp: exp,);
}
Exp getExp(int index) {
return new Exp(
myDCList[index]['dname'],
_getChildren(myDCList[index]['did']),
);
//return new Specialties.fromMap(mylist[index]);
}
}
class Exp {
Exp(this.title, [this.children]);
final String title;
final Future<List<Exp>> children;
}
class ListChild extends StatefulWidget {
ListChild({Key key, this.exp}) : super(key: key);
final Exp exp;
@override
State createState() => new ListChildState();
}
class ListChildState extends State<ListChild> {
//PageStorageKey<ListChildState> _key = new PageStorageKey(ListChild);
@override
Widget build(BuildContext context) {
return new ExpansionTile(
key: new PageStorageKey(ListChild),
title: new Text(widget.exp.title),
children: <Widget>[
new Text(widget.exp.children.title),
],
);
}
}
Reacting to you comment and edit of the question I took the liberty to write a working example. Feel free to edit or comment. I hope, this is what you wanted to achieve.
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('http://174.138.61.246:8080/support/dc/1');
}
@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> json = JSON.decode(response.data.body);
return new MyExpansionTileList(json);
}
},
),
);
}
}
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['did'], element['dname']),
);
});
return children;
}
@override
Widget build(BuildContext context) {
return new ListView(
children: _getChildren(),
);
}
}
class MyExpansionTile extends StatefulWidget {
final int did;
final String name;
MyExpansionTile(this.did, this.name);
@override
State createState() => new MyExpansionTileState();
}
class MyExpansionTileState extends State<MyExpansionTile> {
PageStorageKey _key;
Future<http.Response> _responseFuture;
@override
void initState() {
super.initState();
_responseFuture =
http.get('http://174.138.61.246:8080/support/dcreasons/${widget.did}');
}
@override
Widget build(BuildContext context) {
_key = new PageStorageKey('${widget.did}');
return new ExpansionTile(
key: _key,
title: new Text(widget.name),
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 = JSON.decode(response.data.body);
List<Widget> reasonList = [];
json.forEach((element) {
reasonList.add(new ListTile(
dense: true,
title: new Text(element['reason']),
));
});
return new Column(children: reasonList);
}
},
)
],
);
}
}
Following Rainer Wittmann
approach, I modified it to fit my needs and implemented for Cloud Firestore, but instead of futures
I used streams
.
My basic structure of Cloud Firestore is:
Collection projects
name
Collection surveys:
Solution:
class ProjectList extends StatelessWidget {
ProjectList({this.firestore});
final Firestore firestore;
@override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: firestore.collection('projects').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
//final int projectsCount = snapshot.data.documents.length;
List<DocumentSnapshot> documents = snapshot.data.documents;
return ExpansionTileList(
firestore: firestore,
documents: documents,
);
},
);
}
}
class ExpansionTileList extends StatelessWidget {
final List<DocumentSnapshot> documents;
final Firestore firestore;
ExpansionTileList({this.documents, this.firestore});
List<Widget> _getChildren() {
List<Widget> children = [];
documents.forEach((doc) {
children.add(
ProjectsExpansionTile(
name: doc['name'],
projectKey: doc.documentID,
firestore: firestore,
),
);
});
return children;
}
@override
Widget build(BuildContext context) {
return ListView(
children: _getChildren(),
);
}
}
class ProjectsExpansionTile extends StatelessWidget {
ProjectsExpansionTile({this.projectKey, this.name, this.firestore});
final String projectKey;
final String name;
final Firestore firestore;
@override
Widget build(BuildContext context) {
PageStorageKey _projectKey = PageStorageKey('$projectKey');
return ExpansionTile(
key: _projectKey,
title: Text(
name,
style: TextStyle(fontSize: 28.0),
),
children: <Widget>[
StreamBuilder(
stream: firestore
.collection('projects')
.document(projectKey)
.collection('surveys')
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
//final int surveysCount = snapshot.data.documents.length;
List<DocumentSnapshot> documents = snapshot.data.documents;
List<Widget> surveysList = [];
documents.forEach((doc) {
PageStorageKey _surveyKey =
new PageStorageKey('${doc.documentID}');
surveysList.add(ListTile(
key: _surveyKey,
title: Text(doc['surveyName']),
));
});
return Column(children: surveysList);
})
],
);
}
}
Hope this helps to those lost in nested collections in cloud firestore.
Happy coding!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With