Understanding ListView.builder - flutter

Okay, so I think I am stuck with flutter builder a little bit.
I've created simple app, just to make my question easier:
I have a data class:
class DataLists {
List<ListTile> lists = [
ListTile(
leading: Text('Tile Leading 1'),
title: Text('Tile Title 1'),
subtitle: Text('Tile Subtitle 1'),
trailing: Text('Tile Trailing 1'),
),
ListTile(
leading: Text('Tile Leading 2'),
title: Text('Tile Title 2'),
subtitle: Text('Tile Subtitle 2'),
trailing: Text('Tile Trailing 2'),
),
ListTile(
leading: Text('Tile Leading 3'),
title: Text('Tile Title 3'),
subtitle: Text('Tile Subtitle 3'),
trailing: Text('Tile Trailing 3'),
),
ListTile(
leading: Text('Tile Leading 4'),
title: Text('Tile Title 4'),
subtitle: Text('Tile Subtitle 4'),
trailing: Text('Tile Trailing 4'),
),
ListTile(
leading: Text('Tile Leading 5'),
title: Text('Tile Title 5'),
subtitle: Text('Tile Subtitle 5'),
trailing: Text('Tile Trailing 5'),
),
];
}
And main dart file:
import 'package:flutter/material.dart';
import 'package:learning/data.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: TestTile(),
);
}
}
class TestTile extends StatefulWidget {
#override
_TestTileState createState() => _TestTileState();
}
class _TestTileState extends State<TestTile> {
DataLists dataLists = DataLists();
TextEditingController leadingController = TextEditingController();
TextEditingController titleController = TextEditingController();
TextEditingController subtitleController = TextEditingController();
TextEditingController trailingController = TextEditingController();
Future<String> createDialog(BuildContext context) {
return showDialog(context: context, builder: (context) {
return SimpleDialog(
title: Text('Input data: '),
children: [
TextField(
controller: leadingController,
),
TextField(
controller: titleController,
),
TextField(
controller: subtitleController,
),
TextField(
controller: trailingController,
),
MaterialButton(
child: Text('Submit'),
onPressed: () {
Navigator.of(context).pop(leadingController.text);
setState(() {
List<ListTile> tempList = dataLists.lists;
if (titleController.text.isNotEmpty && leadingController.text.isNotEmpty && subtitleController.text.isNotEmpty && trailingController.text.isNotEmpty) {
tempList.add(
ListTile(
leading: Text(leadingController.text),
title: Text(titleController.text),
subtitle: Text(subtitleController.text),
trailing: Text(trailingController.text),
),
);
dataLists.lists = tempList;
} else {
print('Null values');
}
leadingController.clear();
titleController.clear();
subtitleController.clear();
trailingController.clear();
});
},
),
],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test Tile'),
),
body: Container(
child: SafeArea(
child: ListView(
children: <ListTile>[
for (ListTile e in dataLists.lists)
e
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
createDialog(context);
setState(() {
});
},
child: Icon(Icons.add),
backgroundColor: Colors.blue,
),
);
}
}
The problem is: I cannot make it work in other way. Can someone change my implementation to a ListView.builder? I am stuck a little bit :(
Key goal:
Idea:
Click on a button -> form appear -> after you press a submit button list is updated instantly
I'll add a delete function later, just learning docs, nothing more.
Can someone review my code and, if no one mind, try to rewrite the same idea, but using ListView.builder?
I've tried several times, but cannot get properties correctly from the form, and update listtile using builder, need help
Cheers!

ListView.builder requires a static height, so keep a track on that one. Now, coming to the question, that you want to use ListView.builder. You can do via this
Container(
height: give_your_height,
child: ListView.builder(
shrinkWrap: true,
itemCount: dataLists.lists.length,
itemBuilder: (context, index) {
return dataLists.lists[index];
}
)
)

Try this, it may solve your issue.
ListView(
children: [
for (ListTile e in dataLists.lists)
Card(child: e)
],
),
or with ListView.builder()
ListView.builder(
itemCount: dataLists.lists.length,
itemBuilder: (context, index) {
return dataLists.lists[index];
},
);
Further Reference: https://api.flutter.dev/flutter/material/ListTile-class.html

Related

how get selected index of multiple ExpansionTile in flutter

how get selected index of multiple ExpansionTile in flutter ?
i need sidebar menu with multiple expansiontile and listtile.
how can i get selected index to change selected color menu with provider or bloc ?
children: [
ExpansionTile(
title: Text('main a'),
children: [
ListTile(
title: Text('a1'),
),
ListTile(
title: Text('a2'),
),
ExpansionTile(
title: Text('a3'),
children: [
ListTile(
title: Text('a31'),
),
ListTile(
title: Text('a32'),
),
ListTile(
title: Text('a32'),
),
],
),
],
),
ExpansionTile(
title: Text('main b'),
children: [
ListTile(
title: Text('b1'),
),
ListTile(
title: Text('b2'),
),
ListTile(
title: Text('b3'),
),
],
),
],
You can use onTap from ListTile, and create state variables to hold selected item. Like here I am using String. Based on your data, creating model class or map might be better choice.
String? aValue;
....
ExpansionTile(
title: Text('main a'),
children: [
ListTile(
title: Text('a1'),
onTap: () {
aValue = "a1";
setState(() {});
},
),
You can use a ListView to contain the ExpansionTile widgets and a ListTile widgets. Then you can use a currentIndex variable to keep track of the index of the currently selected menu item. You can use a Provider or BLoC to manage the currentIndex variable and to notify the widget tree when the value of currentIndex changes.
Here is the full code
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => MenuModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ExpansionTile Demo'),
),
body: MenuList(),
);
}
}
class MenuList extends StatelessWidget {
#override
Widget build(BuildContext context) {
final model = Provider.of<MenuModel>(context);
return ListView(
children: <Widget>[
ExpansionTile(
title: Text('Menu 1'),
children: <Widget>[
ListTile(
title: Text('Menu 1.1'),
onTap: () {
model.updateIndex(0);
},
selected: model.currentIndex == 0,
),
ListTile(
title: Text('Menu 1.2'),
onTap: () {
model.updateIndex(1);
},
selected: model.currentIndex == 1,
),
],
),
ExpansionTile(
title: Text('Menu 2'),
children: <Widget>[
ListTile(
title: Text('Menu 2.1'),
onTap: () {
model.updateIndex(2);
},
selected: model.currentIndex == 2,
),
ListTile(
title: Text('Menu 2.2'),
onTap: () {
model.updateIndex(3);
},
selected: model.currentIndex == 3,
),
],
),
],
);
}
}
class MenuModel with ChangeNotifier {
int _currentIndex = 0;
int get currentIndex => _currentIndex;
void updateIndex(int index) {
_currentIndex = index;
notifyListeners();
}
}
This is a hassle to dynamically change the color of the ListTile() which have two different parent widget but with some extra code, you can do the same.
Full Code
// You can also use `Map` but for the sake of simplicity I'm using two separate `List`.
final List<String> _parentlist1 = ["a1", "a2"];
final List<String> _childOfParentlist1 = ["a31", "a32", "a34"];
final List<bool> _isSelectedForParentList1 = List.generate(
2,
(i) =>
false); // Fill it with false initially and this list for all the textList
final List<bool> _isSelectedForChildOfParentList1 =
List.generate(2, (i) => false);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ExpansionTile(
title: const Text('main a'),
children: [
ListView.builder(
itemBuilder: (_, i) {
return ListTile(
tileColor: _isSelectedForParentList1[i]
? Colors.blue
: null, // If current item is selected show blue color
title: Text(_parentlist1[i]),
onTap: () => setState(() => _isSelectedForParentList1[i] =
!_isSelectedForParentList1[i]), // Reverse bool value
);
},
),
ExpansionTile(
title: const Text('a3'),
children: [
ListView.builder(
itemBuilder: (_, i) {
return ListTile(
tileColor: _isSelectedForChildOfParentList1[i]
? Colors.blue
: null, // If current item is selected show blue color
title: Text(_childOfParentlist1[i]),
onTap: () => setState(() =>
_isSelectedForChildOfParentList1[i] =
!_isSelectedForChildOfParentList1[
i]), // Reverse bool value
);
},
),
],
),
],
),
);
}

How to add icon/image along with popup menu in flutter?

Below is my code.
var choices = ['Settings', 'Log Out'];
void choiceAction(String choice){
print(choice);
}
Widget aapBarSection(String title, Color color, BuildContext context){
return AppBar(
title: Text(title, style:TextStyle(fontFamily: 'Poppins-Regular'), ),
centerTitle: true,
backgroundColor: color,
actions: [
PopupMenuButton<String>(
onSelected: choiceAction,
itemBuilder: (BuildContext context){
return choices.map((String choice) {
return PopupMenuItem<String>(
value: choice,
child: Text(choice),
);
}).toList();
},
)
],
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: (){
exit(0);
},
),
);
}
menu items are showing but how to show image/icon as per the items like for settings(settings icon) and for logout(logout icon) with it?
Can anybody help me please!
You can use ListTile as child or Row.
PopupMenuItem<String>(
value: choice,
child: ListTile(
leading: Icon(Icons.work), // your icon
title: Text(choice),
),
)
var choices = [
{'title': 'Home', 'icon': const Icon(Icons.home)},
{'title': 'Profile', 'icon': const Icon(Icons.people)},
{'title': 'Logout', 'icon': const Icon(Icons.logout)}
];
var actions = [
PopupMenuButton<String>(
onSelected: choiceAction,
itemBuilder: (BuildContext ctx) {
return choices.map((ch) {
return PopupMenuItem<String>(
value: ch['title'].toString(),
child: ListTile(
leading: ch['icon'] as Widget,
title: Text(ch['title'].toString())));
}).toList();
})
];
void choiceAction(String choice){
print(choice);
}
class _HomeVendorPageState extends State<HomeVendorPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: actions,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [])));
}
}

Persistent bottom navigation bar flutter

I used a bottom navigation bar in flutter using this widget,
how can I make that bottom navigation bar show on all the pages?
and can I make it appear when I choose a page from drawer??
please help me,
You can actually achieve this with the pageview widget
https://api.flutter.dev/flutter/widgets/PageView-class.html
With this, you can have all the pages inside one class and build the bottom navigation bar underneath the pageview widget. By default the pages are swipeable but you can disable it doing
Scaffold(
body:
Container(
child:
Column(
children: <Widget> [
PageView(
physics:new NeverScrollableScrollPhysics())
controller: _controller,
children: [
MyPage1(),
MyPage2(),
MyPage3(),
],
),
googleNavBar()
]
)
);
May I suggest you to use flutter builtin BottomNavigationBar widget instead of third party widget.
Here is my code you can modify as per you requirement. Hope this will help.
class DashboardScreen extends StatefulWidget {
#override
_DashboardScreenState createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> with SingleTickerProviderStateMixin {
final _selectedItemColor = Colors.white;
final _unselectedItemColor = Color(0xFF828282);
final _selectedBgColor = Color(0xFF00cde7);
final _unselectedBgColor = Colors.transparent;
int _currentIndex = 0;
GlobalKey<ScaffoldState> _key = GlobalKey();
// List of body of current screen you import/create from other dart file.
final List<Widget> _children = [
HomeScreen(),
AppointmentScreen(id: 1),
PaymentScreen(id: 1),
ProfileScreen(id: 1)
];
// List of dynamic app bar for different page. You can also import/create app bar easily
final List<Widget> _childAppBar = [
HomeAppBar(),
AppointmentAppBar(),
PaymentAppBar(),
ProfileAppBar()
];
void _onItemTapped(int index) {
setState(() {
_currentIndex = index;
});
debugPrint("Tapped item : $index");
}
Color _getBgColor(int index) =>
_currentIndex == index ? _selectedBgColor : _unselectedBgColor;
Color _getItemColor(int index) =>
_currentIndex == index ? _selectedItemColor : _unselectedItemColor;
Widget _buildIcon(IconData iconData, String text, int index) => Container(
width: MediaQuery.of(context).size.width,
height: kBottomNavigationBarHeight,
child: Material(
color: _getBgColor(index),
child: InkWell(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
child: Column(
children: [
Icon(iconData, color: _getItemColor(index)),
Text(text,
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500, fontFamily: 'Poppins', color: _getItemColor(index))),
],
),
),
],
),
onTap: () => _onItemTapped(index), // function responsible for navigation on tap
),
),
);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
key: _key,
appBar: _childAppBar.elementAt(_currentIndex), // this is dynamic app bar
body: _children.elementAt(_currentIndex), // this is dynamic body of the current screen
bottomNavigationBar:
BottomNavigationBar(
currentIndex: 0,
type: BottomNavigationBarType.fixed,
iconSize: 30.0,
items: [
BottomNavigationBarItem(
icon: _buildIcon(Icons.home, "Home", 0), // Check this _buildIcon function above
title: SizedBox.shrink(),
),
BottomNavigationBarItem(
icon: _buildIcon(Icons.group, "Appointment", 1),
title: SizedBox.shrink(),
),
BottomNavigationBarItem(
icon: _buildIcon(Icons.add_circle_outline, "Make Payment", 2),
title: SizedBox.shrink(),
),
BottomNavigationBarItem(
icon: _buildIcon( Icons.person_outline, "My Account", 3),
title: SizedBox.shrink(),
),
]
),
drawer: _currentIndex == 0 || _currentIndex == 3 ? Drawer( // check to show drawer on particular screen
child: ListView(
padding: const EdgeInsets.all(0.0),
children: <Widget>[
UserAccountsDrawerHeader(
accountName: Text("Mohammad Gayasuddin"),
accountEmail: Text("ladla8602#gmail.com"),
currentAccountPicture: CircleAvatar(
backgroundColor: Colors.white70,
)),
ListTile(
title: Text('Login'),
trailing: Icon(Icons.lock),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
);
}),
ListTile(
title: Text('Sign Up'),
trailing: Icon(Icons.add_circle_outline),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RegisterScreen(),
),
);
})
],
),
) : PreferredSize(
child: Container(),
preferredSize: Size(0.0, 0.0),
),
),
);
}
}

How to pass parameters in Flutter ListView

i want to pass list of title and description in my list view, i can pass in list view builder, but i don't how to pass in list view.
i want to pass entries array in ListView. without typing each entry i want to show everything in array on cards.
Widget build(BuildContext context) {
final List<String> entries= <String> ['Entry one','Entry Two','Entry Three','Entry one','Entry Two','Entry Three','Entry one','Entry Two','Entry Three','Entry one','Entry Two','Entry Three'];
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: ListView(
children: const <Widget>[
Card(
child: ListTile(
leading: FlutterLogo(size: 40.0),
title: Text('all entries one by one'),
subtitle: Text(
'subtitle'
),
trailing: Icon(Icons.favorite),
isThreeLine: true,
),
),
],
)
);
}
You can use ListView.builder() for this. Here is an example.
class TestPage extends StatelessWidget {
final List<String> entries = <String>[
'Entry one',
'Entry Two',
'Entry Three',
'Entry one',
'Entry Two',
'Entry Three',
'Entry one',
'Entry Two',
'Entry Three',
'Entry one',
'Entry Two',
'Entry Three'
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: Container(
height: 500,
child: ListView.builder(
itemCount: entries.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: ListTile(
leading: FlutterLogo(size: 40.0),
title: Text(entries[index]),
subtitle: Text('subtitle'),
trailing: Icon(Icons.favorite),
isThreeLine: true,
),
);
},
),
),
);
}
}
If you already have array of title and description using the ListView.builder() is the best option here. First make a class for your title and description:
class Info{
String title;
String description;
Info(this.title, this.description);
}
Now create an array of this info class List<Info> _myInfo and populate it with your data. Now you can create your list view like this:
ListView.builder(
itemCount: _myInfo.length,
builder: (context, index) {
return _createCard(index);
}
);
Now you can separate your card creation here like this:
Widget _createCard(int index){
return Card(
child: ListTile(
leading: FlutterLogo(size: 40.0),
//here your title will be create from array for each new item
title: Text(_myInfo[index].title),
subtitle: Text(
'subtitle'
),
trailing: Icon(Icons.favorite),
isThreeLine: true,
),
),
}
Code might contain syntax error feel free to ask if you face any problem

Flutter how to get a popup menu on a ListTile?

I'm trying to get a popupmenu under a ListTile. The title displays a description, the subtitle displays the selected value with some message and the onTap opens the popupmenu in which a user can select a value.
I tried putting a DropdownButtonHideUnderline in the subtitle, but this displays an arrow and does not respond to the ListTile onTab obviously.
How can I get a popupmenu on a ListTile?
Maybe you can try PopuMenuButton,
PopupMenuButton<String>(
onSelected: (String value) {
setState(() {
_selection = value;
});
},
child: ListTile(
leading: IconButton(
icon: Icon(Icons.add_alarm),
onPressed: () {
print('Hello world');
},
),
title: Text('Title'),
subtitle: Column(
children: <Widget>[
Text('Sub title'),
Text(_selection == null ? 'Nothing selected yet' : _selection.toString()),
],
),
trailing: Icon(Icons.account_circle),
),
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
const PopupMenuItem<String>(
value: 'Value1',
child: Text('Choose value 1'),
),
const PopupMenuItem<String>(
value: 'Value2',
child: Text('Choose value 2'),
),
const PopupMenuItem<String>(
value: 'Value3',
child: Text('Choose value 3'),
),
],
)
Take a look at How to open a PopupMenuButton?