Instantiating a List with Provider - flutter

I am trying to learn how to use ChangeNotifierProvider and have gotten stuck. I've setup the class as so:
void main() => runApp(
ChangeNotifierProvider(create: (context) => ItemList(),
child: MyApp(),
)
);
class ItemData {
final String title;
final int score;
ItemData({required this.title, required this.score});
}
class ItemList extends ChangeNotifier{
final _items = [];
void add(item){
_items.add(item);
notifyListeners();
}
void update(){
notifyListeners();
}
}
final itemList = ItemList();
Now I want to create the list:
I'm trying to add items by calling:
itemList.add(ItemData({elements}))
but this isn't working. How do I create my list so I can put it into a Listview Builder?

Try this one:
main.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(
const MyApp(),
);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ChangeNotifierProvider(
create: (context) => ItemList(), child: const NewHomePage()),
);
}
}
class NewHomePage extends StatefulWidget {
const NewHomePage({Key? key}) : super(key: key);
#override
_NewHomePageState createState() => _NewHomePageState();
}
class _NewHomePageState extends State<NewHomePage> {
#override
Widget build(BuildContext context) {
return Consumer<ItemList>(builder: (context, providerItem, child) {
return Scaffold(
appBar: AppBar(
backgroundColor: const Color(0XFF2e3438),
),
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
providerItem.basketItem.isEmpty
? const Text("No item in the list")
: ListView.builder(
itemCount: providerItem.basketItem.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Title: " + providerItem.basketItem[index].title),
);
}),
ElevatedButton(
onPressed: () {
providerItem.addItem(ItemData(
title: DateTime.now().toString(),
score: DateTime.now().month));
print("data added successfully" +
providerItem.basketItem.length.toString());
},
child: const Text("Add Data")),
],
));
});
}
}
item_data.dart
class ItemData {
final String title;
final int score;
ItemData({required this.title, required this.score});
}
item_list.dart
class ItemList extends ChangeNotifier {
List<ItemData> _items = [];
void addItem(ItemData itemData) {
_items.add(itemData);
notifyListeners();
}
List<ItemData> get basketItem {
return _items;
}
}

Related

How to show updated list in shared preferences on UI - Flutter

I am making an app in a flutter in which I can select the contacts from phone book and saving them in shared preferences. No problem in data saving and retrieving but i m struggling with showing the updated list on my UI. It is showing the contacts list but every time I click on Load button it duplicates the list and showing 2 lists , 1 previous and other updated .
how can i show just updated list on UI ?
here is my code:
import 'package:contacts_test/select_contacts.dart';
import 'package:contacts_test/shared_pref.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import 'contact_model.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
SharedPref sharedPref = SharedPref();
ContactModel modelLoad = ContactModel(displayName: 'saniya' , phoneNumber: '324235 ');
List _list = [];
#override
initState() {
super.initState();
// Add listeners to this clas
// loadSharedPrefs();
}
loadSharedPrefs() async {
try {
print('in load shared pref-- getting keys ');
final prefs = await SharedPreferences.getInstance();
final keys = prefs.getKeys();
print('now load shared pref ');
for (String key in keys) {
ContactModel user = ContactModel.fromJson(await sharedPref.read(key));
_list.add(user);
}
print('done load shared pref ');
}
catch (Excepetion) {
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("contacts "),
),
body: Builder(
builder: (context) {
return Column(children: [
RaisedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Plugin1()));
},
child: const Text('fluttercontactpicker - plugin1'),
),
RaisedButton(
onPressed: () async {
await loadSharedPrefs();
},
child: Text('Load', style: TextStyle(fontSize: 20)),
),
Expanded(
child: _list.isNotEmpty ?
ListView.builder(
shrinkWrap: true,
itemCount: _list.length,
itemBuilder: (context, position) {
return ListTile(
leading: Icon(Icons.contacts),
title: Text(
_list[position].displayName.toString()
),
trailing: Icon(Icons.delete));
},
) : Center(child: Text('No list items to show')),
),
]);
}
),
);
}
}
Your loadSharedPrefs(); function adds each contact to the list you show. Every time you press the button, the same elements are added again to the list. There are multiple ways to avoid that. You can: empty the list before filling it, you can write a for loop to loop over the length of the incoming contacts and for each to add it to the list by always starting from index 0. In case you use some kind of replacement or removing method, make sure you call setState(()=> { });
Base on the answer, here is a possible solution:
import 'package:contacts_test/select_contacts.dart';
import 'package:contacts_test/shared_pref.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import 'contact_model.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
SharedPref sharedPref = SharedPref();
ContactModel modelLoad = ContactModel(displayName: 'saniya' , phoneNumber: '324235 ');
List _list = [];
#override
initState() {
super.initState();
// Add listeners to this clas
// loadSharedPrefs();
}
loadSharedPrefs() async {
try {
print('in load shared pref-- getting keys ');
final prefs = await SharedPreferences.getInstance();
final keys = prefs.getKeys();
print('now load shared pref ');
var newList = [];
for (String key in keys) {
ContactModel user = ContactModel.fromJson(await sharedPref.read(key));
newList.add(user);
}
setState(()=> { _list = newList; });
print('done load shared pref ');
}
catch (Excepetion) {
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("contacts "),
),
body: Builder(
builder: (context) {
return Column(children: [
RaisedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Plugin1()));
},
child: const Text('fluttercontactpicker - plugin1'),
),
RaisedButton(
onPressed: () async {
await loadSharedPrefs();
},
child: Text('Load', style: TextStyle(fontSize: 20)),
),
Expanded(
child: _list.isNotEmpty ?
ListView.builder(
shrinkWrap: true,
itemCount: _list.length,
itemBuilder: (context, position) {
return ListTile(
leading: Icon(Icons.contacts),
title: Text(
_list[position].displayName.toString()
),
trailing: Icon(Icons.delete));
},
) : Center(child: Text('No list items to show')),
),
]);
}
),
);
}
}

How to remove space between expanded ExpansionPanels in ExpansionPanelList?

This is an example code for ExpansionPanelList
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
// stores ExpansionPanel state information
class Item {
Item({
required this.expandedValue,
required this.headerValue,
this.isExpanded = false,
});
String expandedValue;
String headerValue;
bool isExpanded;
}
List<Item> generateItems(int numberOfItems) {
return List<Item>.generate(numberOfItems, (int index) {
return Item(
headerValue: 'Panel $index',
expandedValue: 'This is item number $index',
);
});
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<Item> _data = generateItems(8);
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
child: _buildPanel(),
),
);
}
Widget _buildPanel() {
return ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_data[index].isExpanded = !isExpanded;
});
},
children: _data.map<ExpansionPanel>((Item item) {
return ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(item.headerValue),
);
},
body: ListTile(
title: Text(item.expandedValue),
subtitle:
const Text('To delete this panel, tap the trash can icon'),
trailing: const Icon(Icons.delete),
onTap: () {
setState(() {
_data.removeWhere((Item currentItem) => item == currentItem);
});
}),
isExpanded: item.isExpanded,
);
}).toList(),
);
}
}
And it gives the following result:
As you see there is grey space between Panel 0 and Panel 1, and between Panel 1 and Panel 2. Could anyone say how to remove this space, if it is possible?
This space is added by MaterialGap inside source code.
if (_isChildExpanded(index) && index != 0 && !_isChildExpanded(index - 1))
items.add(MaterialGap(
key: _SaltedKey<BuildContext, int>(context, index * 2 - 1)));
You can remove/comment this part or better create a local project file and comment this part.
To use your customized ExpansionPanelList, import your file like
import 'customized_expansionlist.dart' as customExp;
...
customExp.ExpansionPanelList(... customExp.ExpansionPanel(...))

Flutter bloc not working after hot reload

My app using bloc/cubit to display a list of Todo items works fine until I hot reload/hot restart the application!
I have two buttons, when i click these, the cubit state is set to having 3 todo items. Additionally I have two periodic timer which sets the cubit state to having only 1 or 0 todo items again. So the number of items is constantly changing from 1 to 0 until, or if i press a button it momentarily becomes 3.
This works fine until hot reload/restart after which the buttons no longer work! The periodic changes do work however. I can only alleviate this problem by creating my ToDoBloc as a field Initializer within my "MyApp" base widget.
main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:todo/todo_api_controller.dart';
import 'package:todo/todo_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
late TodoBloc _todoBloc; //!!----IF I CREATE THE TODOBLOC HERE EVERYTHING WORKS--!!
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
_todoBloc = TodoBloc(
apiController: TodoApiController(),
);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(_todoBloc),
);
}
}
class MyHomePage extends StatelessWidget {
TodoBloc _todoBloc;
MyHomePage(this._todoBloc, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: BlocProvider<TodoBloc>.value(
value: _todoBloc,
child: BlocBuilder<TodoBloc, TodoBlocState>(
builder: (context, state) {
return Builder(
builder: (context) => Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () => context.read<TodoBloc>().LoadAll(),
child: Text(
'pressme',
style: TextStyle(color: Colors.red),
)),
ListView.builder(
shrinkWrap: true,
itemCount: state.todos.length,
itemBuilder: (context, index) {
var todo = state.todos[index];
return CheckboxListTile(
value: todo.isFinished,
onChanged: (newvalue) {},
);
}),
],
));
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await _todoBloc.LoadAll();
},
),
);
}
}
todo_api_controller.dart
class TodoApiController {
List<Todo> GetAll() {
return [
Todo(id: "asfsdf", name: "this is todo", isFinished: true, finishedOn: DateTime.now()),
Todo(id: "asfsdf", name: "this is todo", isFinished: true, finishedOn: DateTime.now()),
];
}
void Delete(String id) {}
void Update(Todo todo) {}
Todo Create() {
return Todo(id: "asdfsdf");
}
}
class Todo {
final String id;
String name;
bool isFinished;
DateTime? finishedOn;
Todo({required String id, String name = "", bool isFinished = false, DateTime? finishedOn = null})
: id = id,
name = name,
isFinished = isFinished,
finishedOn = finishedOn {}
}
todo_bloc.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:todo/todo_api_controller.dart';
class TodoBloc extends Cubit<TodoBlocState> {
static int numberOfInstances = 0;
int myInstance = -1;
TodoApiController _apiController;
List<Todo> todos = [];
TodoBloc({required TodoApiController apiController})
: _apiController = apiController,
super(TodoBlocState(todos: [TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null)])) {
numberOfInstances++;
myInstance = numberOfInstances;
Timer.periodic(Duration(seconds: 2), (s) => emit(TodoBlocState()));
Future.delayed(
Duration(seconds: 1),
() => Timer.periodic(
Duration(seconds: 2), (s) => emit(TodoBlocState(todos: [TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null)]))));
}
Future<void> LoadAll() async {
/* var newTodos = _apiController.GetAll();
todos.clear();
todos.addAll(newTodos);
var newState = MakeState();
emit(newState);*/
emit(TodoBlocState(todos: [
TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null),
TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null),
TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null),
]));
}
TodoBlocState MakeState() {
return TodoBlocState(
todos: todos
.map((e) => TodoItemState(
id: e.id,
finishedOn: e.finishedOn,
isFinished: e.isFinished,
name: e.name,
))
.toList(),
);
}
}
class TodoBlocState {
final List<TodoItemState> todos = [];
TodoBlocState({List<TodoItemState>? todos}) {
this.todos.addAll(todos ?? []);
}
}
class TodoItemState {
final String id;
final String name;
final bool isFinished;
final DateTime? finishedOn;
TodoItemState({required this.id, required this.name, required this.isFinished, required this.finishedOn});
}
I cant figure out why this is, especially with hot restart, as this should reset all application state.
EDIT: the issue appears after a hot reload(not hot restart) but cannot be fixed by hot restart
EDIT2: the issue is fixed by adding a GlobalKey() to the MyHomePage class. Though I cannot understand why. Can someone explain this to me?
This is happening because you're initializing your TodoBloc inside a build function.
Hot reload rebuilds your widgets so it triggers a new call to their build functions.
You should convert it into a StatefulWidget and initilize your TodoBloc inside the initState function:
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late TodoBloc _todoBloc;
#override
void initState() {
super.initState();
_todoBloc = TodoBloc(
apiController: TodoApiController(),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(_todoBloc),
);
}
}
You really don't need to declare and initialize your TodoBloc at the top of the widget tree then pass it all the way down. BlocProvider creates a new instance that is accessible via context.read<TodoBloc>().
Your MyApp could look like this.
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocProvider(
create: (context) => TodoBloc(apiController: TodoApiController()), // this is your bloc being created and initialized
child: MyHomePage(),
),
);
}
}
And MyHomePage could be simplified. Note the lack of BlocProvider.value and Builder. All you need is a BlocBuilder and the correct instance of TodoBloc is always accessible with context.read<TodoBloc>().
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: BlocBuilder<TodoBloc, TodoBlocState>(
builder: (context, state) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () => context.read<TodoBloc>().LoadAll(),
child: Text(
'pressme',
style: TextStyle(color: Colors.red),
),
),
ListView.builder(
shrinkWrap: true,
itemCount: state.todos.length,
itemBuilder: (context, index) {
var todo = state.todos[index];
return CheckboxListTile(
value: todo.isFinished,
onChanged: (newvalue) {},
);
},
),
],
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await context.read<TodoBloc>().LoadAll();
},
),
);
}
}

Streambuilder snapshot is not getting out of waiting state while using bloc

I am trying to reproduce the hacker news app as shown in this video : https://youtu.be/fahC3ky_zW0. However in my StreamBuilder Snapshot connection state is always coming out to be 'waiting'. Hence my app is not able to populate data.
File: hn_bloc.dart
class HackerNewsBloc {
final _articlesSubject = BehaviorSubject<UnmodifiableListView<Article>>();
var _articles = <Article>[];
List<int> _ids = [
23361987,
23366546,
];
HackerNewBlock() {
_updateArticles().then((_) {
_articlesSubject.add(UnmodifiableListView(_articles));
});
}
Stream<UnmodifiableListView<Article>> get articles => _articlesSubject.stream;
Future<Article> _getArticle(int id) async {
final storyUrl = 'https://hacker-news.firebaseio.com/v0/item/$id.json';
final storyRes = await http.get(storyUrl);
return parseArticle(storyRes.body);
}
Future<Null> _updateArticles() async {
final futureArticles = _ids.map((id) => _getArticle(id));
final articles = await Future.wait(futureArticles);
_articles = articles;
return null;
}
}
File main.dart
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:hackernews/src/article.dart';
import 'package:hackernews/src/hn_bloc.dart';
import 'dart:collection';
import 'dart:async';
void main() {
final hnBloc = HackerNewsBloc();
runApp(MyApp(
bloc: hnBloc,
));
}
class MyApp extends StatelessWidget {
final HackerNewsBloc bloc;
MyApp({
Key key,
this.bloc,
}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hacker News',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(
title: 'Hacker News',
bloc: bloc,
),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
final HackerNewsBloc bloc;
MyHomePage({Key key, this.title, this.bloc}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: StreamBuilder<UnmodifiableListView<Article>>(
stream: widget.bloc.articles,
initialData: UnmodifiableListView<Article>([]),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Press button to start.');
case ConnectionState.active:
case ConnectionState.waiting:
return Text('Awaiting result...');
case ConnectionState.done:
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
return ListView(
children: snapshot.data.map(_buildItem).toList(),
);
// You can reach your snapshot.data['url'] in here
}
return null; // unreachable
},
),
);
}
Widget _buildItem(Article article) {
print(article);
return Padding(
key: Key(article.title),
padding: const EdgeInsets.all(16.0),
child: ExpansionTile(
title: Text(
article.title,
style: TextStyle(fontSize: 24),
),
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text("${article.type}"),
IconButton(
icon: Icon(Icons.launch),
onPressed: () async {
if (await canLaunch(article.url)) {
launch(article.url);
}
},
),
],
),
],
),
);
}
}

How to add subitems to ExpansionPanelList - ListView.Builder?

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: MyStatefulWidget(),
),
);
}
}
class Item {
Item({
this.expandedValue,
this.headerValue,
this.isExpanded = false,
});
String expandedValue;
String headerValue;
bool isExpanded;
List<Item> _subItems;
List<Item> get getSubItems => _subItems;
set subItems(List<Item> subItems) {
_subItems = subItems;
}
}
List<Item> generateItems(int numberOfItems) {
return List.generate(numberOfItems, (int index) {
return Item(
headerValue: 'Panel $index',
expandedValue: 'This is item number $index',
);
});
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
List<Item> _data = generateItems(8);
#override
void initState() {
_data.forEach((element) {
element.subItems = generateItems(3);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
child: _buildPanel(),
),
);
}
Widget _buildPanel() {
return ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_data[index].isExpanded = !isExpanded;
});
},
children: _data.map<ExpansionPanel>((Item item) {
return ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(item.headerValue),
);
},
body: Container(
child: ListView.builder(
itemCount: item._subItems.length,
itemBuilder: (context, position) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
position.toString(),
style: TextStyle(fontSize: 22.0),
),
),
);
},
),
),
isExpanded: item.isExpanded,
);
}).toList(),
);
}
}
I think, ExpansionTile is what you need
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: MyStatefulWidget(),
),
);
}
}
class Item {
Item({
this.headerValue,
this.expandedValue,
});
String headerValue;
String expandedValue;
List<Item> subItems;
}
List<Item> generateItems(int numberOfItems) {
return List.generate(numberOfItems, (int index) {
return Item(
headerValue: 'Panel $index',
expandedValue: 'This is item number $index',
);
});
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
List<Item> _data = generateItems(8);
#override
void initState() {
_data.forEach((element) {
element.subItems = generateItems(3);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return ListView.separated(
itemCount: _data.length,
separatorBuilder: (context, index){
return const Divider(height: 1.0);
},
itemBuilder: (context, index){
final item = _data[index];
return ExpansionTile(
title: Text(item.headerValue),
subtitle: Text(item.expandedValue),
children: item.subItems.map((subItem){
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
subItem.expandedValue,
style: TextStyle(fontSize: 22.0),
),
),
);
}).toList(),
);
},
);
}
}