How to create a dynamic tab bar in Flutter? - flutter

I have two tabs that are built from the repository class. I need to filter the items that are shown on them according to nationalities, but the items are rendered only on the first tab. The second tab doesn't show anything, I believe it has to do with Stateful and Stateless.
How can I run repository class each time I change tab?
The code below is about Tab and TabView
import 'package:flutter/material.dart';
import 'package:flutterbol/models/teams_model.dart';
import 'package:flutterbol/repository/teams_repository.dart';
class TeamsScreen extends StatefulWidget {
#override
_TeamsScreenState createState() => _TeamsScreenState();
}
class _TeamsScreenState extends State<TeamsScreen> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: choices.length,
child: Scaffold(
appBar: AppBar(
title: const Text('Tabbed AppBar'),
bottom: TabBar(
isScrollable: true,
tabs: choices.map((Choice choice) {
return Tab(
text: choice.title,
icon: Icon(choice.icon),
);
}).toList(),
),
),
body: TabBarView(
children: choices.map((Choice choice) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: ChoiceCard(choice: choice),
);
}).toList(),
),
),
),
);
}
}
class Choice {
const Choice({this.title, this.icon});
final String title;
final IconData icon;
}
const List<Choice> choices = const <Choice>[
const Choice(title: 'NATIONALS', icon: Icons.flag),
const Choice(title: 'INTERNATIONALS', icon: Icons.outlined_flag),
];
class ChoiceCard extends StatelessWidget {
const ChoiceCard({Key key, this.choice}) : super(key: key);
final Choice choice;
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<TeamsModel>>(
future: TeamsRepository().findAllAsync(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return buildListView(snapshot.data);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
));
}
ListView buildListView(List<TeamsModel> teams) {
return ListView.builder(
itemCount: teams == null ? 0 : teams.length,
//itemCount: teams.length,
itemBuilder: (BuildContext ctx, int index) {
return teamCard(teams[index]);
},
);
}
Card teamCard(TeamsModel team) {
if (team.nacionalidade == choice.title) {
return Card(
child: Text(team.name),
);
}
}
}
Tabs and Cards

You can copy paste run full code below
You need to return Container() in else condition and return type is Widget not Card
code snippet
Widget teamCard(TeamsModel team) {
if (team.nacionalidade == choice.title) {
return Card(
child: Text(team.name),
);
} else {
return Container();
}
}
working demo
full code
import 'package:flutter/material.dart';
class TeamsModel {
String nacionalidade;
String name;
TeamsModel(this.name, this.nacionalidade);
}
class TeamsRepository {
Future<List<TeamsModel>> findAllAsync() {
return Future.value([
TeamsModel("a", "NATIONALS"),
TeamsModel("b", "NATIONALS"),
TeamsModel("c", "INTERNATIONALS"),
TeamsModel("d", "INTERNATIONALS")
]);
}
}
class TeamsScreen extends StatefulWidget {
#override
_TeamsScreenState createState() => _TeamsScreenState();
}
class _TeamsScreenState extends State<TeamsScreen> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: choices.length,
child: Scaffold(
appBar: AppBar(
title: const Text('Tabbed AppBar'),
bottom: TabBar(
isScrollable: true,
tabs: choices.map((Choice choice) {
return Tab(
text: choice.title,
icon: Icon(choice.icon),
);
}).toList(),
),
),
body: TabBarView(
children: choices.map((Choice choice) {
print(choice.title);
return Padding(
padding: const EdgeInsets.all(16.0),
child: ChoiceCard(choice: choice),
);
}).toList(),
),
),
),
);
}
}
class Choice {
const Choice({this.title, this.icon});
final String title;
final IconData icon;
}
const List<Choice> choices = const <Choice>[
const Choice(title: 'NATIONALS', icon: Icons.flag),
const Choice(title: 'INTERNATIONALS', icon: Icons.outlined_flag),
];
class ChoiceCard extends StatelessWidget {
const ChoiceCard({Key key, this.choice}) : super(key: key);
final Choice choice;
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<TeamsModel>>(
future: TeamsRepository().findAllAsync(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return buildListView(snapshot.data);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
));
}
ListView buildListView(List<TeamsModel> teams) {
return ListView.builder(
itemCount: teams == null ? 0 : teams.length,
//itemCount: teams.length,
itemBuilder: (BuildContext ctx, int index) {
return teamCard(teams[index]);
},
);
}
Widget teamCard(TeamsModel team) {
if (team.nacionalidade == choice.title) {
return Card(
child: Text(team.name),
);
} else {
return Container();
}
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: TeamsScreen(),
);
}
}

Related

Flutter bottomNavigationBar not changing pages (when it's the "same page")

I have a bottom navigation bar and realized that the different pages/widgets that the navigator was going to were pretty much the exact same page (except for 2 parameters that changed). So instead of creating 2 pages/widgets which were pretty much identical (with only 2 differing parameters), I wanted to consolidate it into only one widget and pass the parameters from the page with the bottom navigator. The problem is that now that I did that it won't change the page it displays, or at least it won't change consistently (it usually will only show the page that corresponds to the first tab in the navigator (i.e., index = 0)). Here is my page with the bottom navigator:
class FreestylePage extends StatefulWidget {
const FreestylePage({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() {
return _FreestylePageState();
}
}
class _FreestylePageState extends State<FreestylePage> {
int _currentIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: showCategory(_currentIndex),
)),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.looks_one_outlined),
label: 'Single rope',
backgroundColor: Color.fromRGBO(204, 16, 138, 1)),
BottomNavigationBarItem(
icon: Icon(Icons.looks_two_outlined),
label: 'Double dutch',
backgroundColor: Color.fromRGBO(204, 16, 138, 1)),
],
onTap: (index) {
if (mounted) {
setState(() {
_currentIndex = index;
});
}
},
),
);
}
showCategory(index) {
if (index == 0) {
return [
WorkoutListPage(categoryIndex: 2, subCategories: Utils.srfDropdown)
];
} else {
return [
WorkoutListPage(categoryIndex: 3, subCategories: Utils.ddfDropdown)
];
}
}
}
And the WorkoutListPage looks as follows:
class WorkoutListPage extends StatefulWidget {
final int categoryIndex;
final List<String> subCategories;
const WorkoutListPage(
{Key? key, required this.categoryIndex, required this.subCategories})
: super(key: key);
#override
State<StatefulWidget> createState() {
return _WorkoutListPageState();
}
}
class _WorkoutListPageState extends State<WorkoutListPage> {
bool isLoading = true;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) =>
FutureBuilder<List<Map<String, dynamic>>>(
future: MyCard.getData(widget.categoryIndex, widget.subCategories)!
.whenComplete(() => setState(() {
isLoading = false;
})),
builder: ((context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return FutureBuilder<List<MyCard>>(
future: MyCard.readData(snapshot.data),
builder: (context, cards) {
if (cards.hasData) {
final card = cards.data!;
return Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: card.length,
itemBuilder: (context, index) {
return MyCard.buildCard(card[index], context);
},
),
);
} else {
return const Text("No data");
}
});
} else {
return isLoading
? Column(
children: const [CircularProgressIndicator()],
)
: const Text("You do not have any workouts yet");
}
}),
);
}
This doesn't work, but ironically if I change my showCategory function in the widget with the bottom navigation bar to the following:
showCategory(index) {
if (index == 0) {
return [
WorkoutListPage(categoryIndex: 2, subCategories: Utils.srfDropdown)
];
} else {
return [const FreestyleDDPage()];
}
}
where the FreestyleDDPage is the following:
class FreestyleDDPage extends StatefulWidget {
const FreestyleDDPage({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() {
return _FreestyleDDPageState();
}
}
class _FreestyleDDPageState extends State<FreestyleDDPage> {
var isLoading = true;
#override
Widget build(BuildContext context) =>
FutureBuilder<List<Map<String, dynamic>>>(
future: MyCard.getData(3, Utils.ddfDropdown)!
.whenComplete(() => setState(() {
isLoading = false;
})),
builder: ((context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return FutureBuilder<List<MyCard>>(
future: MyCard.readData(snapshot.data),
builder: (context, cards) {
if (cards.hasData) {
final card = cards.data!;
return Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: card.length,
itemBuilder: (context, index) {
return MyCard.buildCard(card[index], context);
},
),
);
} else {
return const Text("No data");
}
});
} else {
return isLoading
? Column(
children: const [CircularProgressIndicator()],
)
: const Text("You do not have any workouts yet");
}
}),
);
}
then it works.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
CustomWidgetWithParametr(index: 0 , categoryName: "HOME"),
CustomWidgetWithParametr(index: 1 , categoryName: "BUSINES"),
CustomWidgetWithParametr(index: 2 , categoryName: "SCHOOL"),
CustomWidgetWithParametr(index: 3 , categoryName: "Settings"),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
backgroundColor: Colors.red,
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
backgroundColor: Colors.green,
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
backgroundColor: Colors.purple,
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
backgroundColor: Colors.pink,
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
class CustomWidgetWithParametr extends StatefulWidget {
const CustomWidgetWithParametr({Key? key, required this.index, required this.categoryName}) : super(key: key);
final int index;
final String categoryName;
#override
State<CustomWidgetWithParametr> createState() => _CustomWidgetWithParametrState();
}
class _CustomWidgetWithParametrState extends State<CustomWidgetWithParametr> {
#override
Widget build(BuildContext context) {
return
Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(widget.index.toString()),
Text(widget.categoryName),
],
);
}
}

Flutter unable to update dynamic TextEditingController text

I'm generating TextFormFields dynamically and assigning unique TextEditingControllers individually. I then only update the text of the TextFormField that's currently in focus
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
debugPrint('Current Controller: $textEditingController');
setState(() {
_selectedField = textEditingController;
});
},
),
);
n--;
}
return Column(children: listForm);
}
with
InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
/// On tap is able to fetch the correct active TextFormField
debugPrint('Active field: $_selectedField');
_selectedField!.text = 'Hello!'; // doesn't work
setState(() {
/// Setting TextEditingController.text doesn't work
_selectedField!.text = 'Item $index'; // doesn't work
});
}
},
I'm able to successfully fetch the TextEditingController, but unable to update their text. Any idea why TextEditingController.text doesnt work?
Minimal repro
import 'package:flutter/material.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 MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController? _selectedField;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: textField(3)),
Expanded(flex: 1, child: listItems()),
],
),
),
),
);
}
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
debugPrint('Current Controller: $textEditingController');
setState(() {
_selectedField = textEditingController;
});
},
),
);
n--;
}
return Column(children: listForm);
}
ListView listItems() {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
/// On tap is able to fetch the correct active TextFormField
debugPrint('Active field: $_selectedField');
_selectedField!.text = 'Hello!'; // doesn't work
setState(() {
/// Setting TextEditingController.text doesn't work
_selectedField!.text = 'Item $index'; // doesn't work
});
}
},
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}
TextEditingValue() will work:
import 'package:flutter/material.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 MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController? _selectedField = TextEditingController();
List<Widget> listForm = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: textField(3)),
Expanded(flex: 1, child: listItems()),
],
),
),
),
);
}
Column textField(int n) {
while (n > 0) {
TextEditingController _textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: _textEditingController,
onTap: () {
_selectedField = _textEditingController;
debugPrint( 'selected' + _selectedField!.value.text );
debugPrint('main' + _textEditingController.toString());
},
),
);
n--;
}
return Column(children: listForm);
}
ListView listItems() {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
_selectedField!.value =
TextEditingValue(text: 'Item $index'); // doesn't work
debugPrint(_selectedField?.value.text);
debugPrint(_selectedField.hashCode.toString());
debugPrint('Item $index');
}
},
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}

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(),
);
},
);
}
}

Flutter Trouble with multiselect checkboxes - data from Firestore

The following code displays the items listed in my collection (Firestore)
I am attempting to create the ability to check any item(s) and then have those items store into a "Favorites" on the next screen.
Currently, the checkboxes are an all or nothing. Either all items are unchecked or checked once tapped.
class _SelectScreenState extends State<SelectScreen> {
bool _isChecked = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Select Exercises')),
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('exercises').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot)
{
return ListView(
padding: const EdgeInsets.only(top: 20.0),
children: snapshot.map((data) => _buildListItem(context,
data)).toList(),
);
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Checkbox(
value: _isChecked,
onChanged: (bool value) {
setState(() {
_isChecked = value;
});
},
)
),
),
);
}
}
class Record {
final String name;
final DocumentReference reference;
Record(this.name, this.reference);
Record.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['name'] != null),
name = map['name'];
Record.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Record<$name:>";
}
It is because you are making use of a single variable for all the checkboxes.
To fix that you could create a dedicated stateful widget, which would handle the state of each of the checkbox's separately from the rest.
So you could replace your ListTile with something like
Exercise(
title: record.name,
)
and then you could define the Exercise widget as follows
class Exercise extends StatefulWidget {
final String title;
Exercise({this.title});
#override
_ExerciseState createState() => _ExerciseState();
}
class _ExerciseState extends State<Exercise> {
bool selected = false;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(widget.title),
trailing: Checkbox(
value: selected,
onChanged: (bool val) {
setState(() {
selected = val;
});
}),
);
}
}
Here is a complete working example
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
children: <Widget>[
Exercise(
title: "Exercises 1",
),
Exercise(
title: "Exercises 2",
),
],
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class Exercise extends StatefulWidget {
final String title;
Exercise({this.title});
#override
_ExerciseState createState() => _ExerciseState();
}
class _ExerciseState extends State<Exercise> {
bool selected = false;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(widget.title),
trailing: Checkbox(
value: selected,
onChanged: (bool val) {
setState(() {
selected = val;
});
}),
);
}
}
Because you have a global variable _isChecked, this needs to be created with each listTile.
Try moving the variable
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
bool _isChecked = false; //try moving it here
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Checkbox(
value: _isChecked,
onChanged: (bool value) {
setState(() {
_isChecked = value;
});
},
)
),
),
);
}

Flutter auto-suggest TextField

I am developing a Flutter app, which contains a TextFormField/TextField inside Form. One of the TextFields will be auto suggest/complete.
What is the proper widget to show a list of data when the TextField input changes?
Flutter has an upcoming AutoComplete feature and you can track its progress in this thread. Aside from using the mentioned packages in the comments, you can also try using RawAutocomplete for your Text[Form]Field while waiting for the AutoComplete feature to be released. You need to be on master channel to try this though.
Here's a sample following this demo.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _textFieldHints = List<String>();
var _textFieldController;
#override
void dispose() {
super.dispose();
_textFieldController.dispose();
}
_addAutoFillText() {
if (_textFieldController.text.isEmpty) {
// Don't add input text if TextFormField is empty
debugPrint('_textFieldController.text is empty');
} else {
debugPrint('${_textFieldController.text} added');
setState(() {
// Add TextFormField String to List
_textFieldHints.add(_textFieldController.text);
// Clear TextFormField after adding
_textFieldController.clear();
});
debugPrint('_textFieldHints contains:');
_textFieldHints.sort();
_textFieldHints.forEach((text) {
debugPrint('$text');
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
padding: EdgeInsets.all(16.0),
child: RawAutocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == null ||
textEditingValue.text == '') {
return const Iterable<String>.empty();
}
return _textFieldHints.where((String option) {
return option
.toLowerCase()
.contains(textEditingValue.text.toLowerCase());
});
},
onSelected: (String selection) {
debugPrint('$selection selected');
},
fieldViewBuilder: (BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted) {
_textFieldController = textEditingController;
return TextFormField(
controller: _textFieldController,
focusNode: focusNode,
onFieldSubmitted: (String value) {
onFieldSubmitted();
},
);
},
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<String> onSelected,
Iterable<String> options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: Container(
height: 200.0,
child: ListView.builder(
padding: EdgeInsets.all(8.0),
itemCount: options.length + 1,
itemBuilder: (BuildContext context, int index) {
if (index >= options.length) {
return TextButton(
child: const Text('clear'),
onPressed: () {
_textFieldController.clear();
},
);
}
final String option = options.elementAt(index);
return GestureDetector(
onTap: () {
onSelected(option);
},
child: ListTile(
title: Text(option),
),
);
},
),
),
),
);
},
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _addAutoFillText(),
child: Icon(Icons.add),
),
);
}
}