I've made a simple search bar for my DataTable list, but the problem is I can't return just the item I search for but instead I get empty fields and the item I search for. I've tried various things, but I get the error that I need rows as much as I have columns, so this is the only way for now that I've made it to work.
But I wanted it to make it like this:
Here is the code:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/vehicle.dart';
import 'services/vehicle_api.dart';
import 'models/vehicle_data_provider.dart';
class VehicleList extends StatefulWidget {
#override
_VehicleList createState() => _VehicleList();
}
class _VehicleList extends State<VehicleList> {
TextEditingController controller = TextEditingController();
String _searchResult = '';
_getPosts() async {
HomePageProvider provider =
Provider.of<HomePageProvider>(context, listen: false);
var postsResponse = await fetchVehicles();
if (postsResponse.isSuccessful) {
provider.setPostsList(postsResponse.data, notify: false);
} else {
provider.mergePostsList(
postsResponse.data,
);
}
provider.setIsHomePageProcessing(false);
}
#override
void initState() {
_getPosts();
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Card(
child: new ListTile(
leading: new Icon(Icons.search),
title: new TextField(
controller: controller,
decoration: new InputDecoration(
hintText: 'Search', border: InputBorder.none),
onChanged: (value) {
setState(() {
_searchResult = value;
});
}),
trailing: new IconButton(
icon: new Icon(Icons.cancel),
onPressed: () {
setState(() {
controller.clear();
_searchResult = '';
});
},
),
),
),
Consumer<HomePageProvider>(
builder: (context, vehicleData, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.all(
Radius.circular(12.0),
),
),
child: SingleChildScrollView(
child: DataTable(
columnSpacing: 30,
columns: <DataColumn>[
DataColumn(
numeric: false,
label: Text(
'Friendly Name',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
DataColumn(
label: Text(
'Licence Plate',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
DataColumn(
label: Text(
'Delete',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
],
rows: List.generate(
vehicleData.postsList.length,
(index) {
VehicleData post = vehicleData.getPostByIndex(index);
return post.licencePlate
.toLowerCase()
.contains(_searchResult) ||
'${post.model}'
.toLowerCase()
.contains(_searchResult) ||
'${post.make}'
.toLowerCase()
.contains(_searchResult) ||
post.type
.toLowerCase()
.contains(_searchResult)
? DataRow(
cells: <DataCell>[
DataCell(
Text('${post.friendlyName}'),
),
DataCell(
Text('${post.licencePlate}'),
),
DataCell(
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
vehicleData.deletePost(post);
},
),
),
],
)
: DataRow(
/// This is the part where I return empty rows with one row with the search bar results, so I assume this must me changed
cells: <DataCell>[
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
],
);
},
),
),
),
),
],
);
},
),
],
);
}
}
Can't seem to figure this one out. Thanks in advance for the help!
Okay after your comment i finally made it work like i think you want. The idea is to uses two lists instead of one and not using the List.generate method because of that empty row. When you change the _searchResult value you filter the userFiltered list with the original values coming from the users lists.
I used the flutter sample for DataTable with those edits and it works:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// This is the main application widget.
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: MyStatelessWidget(),
),
);
}
}
class User{
String name;
int age;
String role;
User({this.name, this.age, this.role});
}
/// This is the stateless widget that the main application instantiates.
class MyStatelessWidget extends StatefulWidget {
MyStatelessWidget({Key key}) : super(key: key);
#override
_MyStatelessWidgetState createState() => _MyStatelessWidgetState();
}
class _MyStatelessWidgetState extends State<MyStatelessWidget> {
List<User> users = [User(name: "Sarah", age: 19, role: "Student"), User(name: "Janine", age: 43, role: "Professor")];
List<User> usersFiltered = [];
TextEditingController controller = TextEditingController();
String _searchResult = '';
#override
void initState() {
super.initState();
usersFiltered = users;
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Card(
child: new ListTile(
leading: new Icon(Icons.search),
title: new TextField(
controller: controller,
decoration: new InputDecoration(
hintText: 'Search', border: InputBorder.none),
onChanged: (value) {
setState(() {
_searchResult = value;
usersFiltered = users.where((user) => user.name.contains(_searchResult) || user.role.contains(_searchResult)).toList();
});
}),
trailing: new IconButton(
icon: new Icon(Icons.cancel),
onPressed: () {
setState(() {
controller.clear();
_searchResult = '';
usersFiltered = users;
});
},
),
),
),
DataTable(
columns: const <DataColumn>[
DataColumn(
label: Text(
'Name',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
DataColumn(
label: Text(
'Age',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
DataColumn(
label: Text(
'Role',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
],
rows: List.generate(usersFiltered.length, (index) =>
DataRow(
cells: <DataCell>[
DataCell(Text(usersFiltered[index].name)),
DataCell(Text(usersFiltered[index].age.toString())),
DataCell(Text(usersFiltered[index].role)),
],
),
),
),
],
);
}
}
OLD POST:
I was looking for a way to filter a datatable and your problem fixed mine thanks ( i will try to help you now!). By using a PaginatedDataTable widget instead of DataTable i can achieve the result you want to. The idea is to filter the list before you pass it to the source property. This is a part of the code i used to filter my list. Inside the switch block i filter it to remove the other elements:
switch(filter){
case "Id d'expédition":
expeditionsList = expeditionsList.where((e) => e.expeditionId.toLowerCase() == stringToSearch.toLowerCase()).toList();
break;
}
return PaginatedDataTable(
showCheckboxColumn: false,
rowsPerPage: 5,
source: DataTableSourceExpedition(
expeditions: expeditionsList,
onRowClicked: (index) async {
await ExpeditionRowDialog.buildExpeditionRowDialog(
context, expeditionsList[index].expeditionsDetails)
.show();
},
header: Container(
width: 100,
child: Text("Expéditions"),
),
columns: [
DataColumn(
label: Text("Id d'expédition"), numeric: false, tooltip: "id"),
],
);
Then i need to pass the data to the table by using the source property which expects a DataTableSource object. I created a separate class which extends DataTableSource. I pass the filtered list as a parameter of this class and override the methods of the DataTableSource class:
class DataTableSourceExpedition extends DataTableSource {
List<Expedition> expeditions = List();
Function onRowClicked;
Function onDeleteIconClick;
final df = DateFormat('dd.MM.yyyy');
DataTableSourceExpedition({this.expeditions, this.onRowClicked,
this.onDeleteIconClick});
DataRow getRow(int index) {
final _expedition = expeditions[index];
return DataRow.byIndex(
index: index,
cells: <DataCell>[
DataCell(Text("${_expedition.expeditionId}")),
DataCell(IconButton(
icon: Icon(Icons.delete_forever, color: kReturnColor,),
onPressed: (){onDeleteIconClick(index);},
))
],
onSelectChanged: (b) => onRowClicked(index));
}
bool get isRowCountApproximate => false;
int get rowCount => expeditions.length;
int get selectedRowCount => 0;
}
Like this, i can get the only item filtered without the need of adding an empty row as you can see on the image below:
It works also if the list is empty.
Related
I have added a list to DataRow from API by List.generate, but when I try to create a common DataTable for reusing it, how can I add the list.generate while calling this widget. is there any way to do that? or is there any other way apart from list.generate?
Reusable or Common Widget.
class MyPurchaseDataTable extends StatelessWidget {
final String column1;
final String column2;
final String column3;
final String row1;
final String row2;
final String row3;
const MyPurchaseDataTable({
Key? key,
required this.row1,
required this.row2,
required this.row3,
required this.column1,
required this.column2,
required this.column3,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return FittedBox(
child: DataTable(
columns: [
const DataColumn(
label: CustomUnderlineText(text: ""),
),
DataColumn(
label: CustomUnderlineText(text: column1),
),
DataColumn(
label: CustomUnderlineText(text: column2),
),
DataColumn(
label: CustomUnderlineText(text: column3),
)
],
rows: [
DataRow(
cells: <DataCell>[
DataCell(Icon(
Icons.square,
size: 12.0.sp,
color: colorPrimary,
)),
DataCell(CustomTextDataCell(text: row1)),
DataCell(CustomTextDataCell(text: row2)),
DataCell(CustomTextDataCell(text: row3))
],
),
],
),
);
}
}
Usually i call like this
rows: List.generate(
controller.myPackageList.length,
(index) {
var myAttenList = controller.myPackageList[index];
return DataRow(cells: [
DataCell(
Text(myAttenList.xattendancedate.toString()),
),
DataCell(
Text(myAttenList.xattendancetime.toString()),
),
DataCell(
Text(myAttenList.xreaderName.toString()),
),
]);
},
).toList(),
Controller
class MyPackagesController extends GetxController {
SecureStorage secureStorage = SecureStorage();
final myPackageList = <MyPackageModel>[].obs;
#override
void onInit() async {
super.onInit();
await fetchMyAttendance();
}
Future fetchMyAttendance() async {
var response = await MyPackageRepo().myPackage();
if (response.status == ApiResponseStatus.completed) {
if (response.data != null) {
myPackageList.value = response.data!;
}
} else {
Get.defaultDialog(title: response.message.toString());
}
}
}
Part of View
Column(
children: [
PurchasePackageHeading(
heading: "My Membership Package",
color: colorPrimary,
height: 46.0.h,
style: TextStyle(fontSize: 18.0.sp, color: Colors.white),
),
CustomSizedBox(height: 4.0.h),
MyPurchaseDataTable(
length: controller.myPackageList.length,
myList: controller.myPackageList,
row1: 'Package Name', // i want to assign the list here
row2: '01-11-2021', // i want to assign the list here
row3: '01-11-2021', // i want to assign the list here
column1: 'Package Name',
column2: 'Start Date',
column3: 'Expire Date',
),
PurchasePackageHeading(
heading: "My Personal Training",
color: colorPrimary,
height: 46.0.h,
style: TextStyle(fontSize: 18.0.sp, color: Colors.white),
),
CustomSizedBox(height: 4.0.h),
const MyPurchaseDataTable(
row1: 'Package Name', // i want to assign the list here
row2: '01-11-2021', // i want to assign the list here
row3: '01-11-2021', // i want to assign the list here
column1: 'Package Name',
column2: 'Start Date',
column3: 'Expire Date',
),
],
),
Recently implemented a tagForm widget at "+" button press, I want to delete those widgets now at "delete" button press, but right now, even when I press the "delete" button, nothing happens.
How can I solve this?
Any help appreciated!
code:
import 'package:flutter/material.dart';
import '../database/firestoreHandler.dart';
import '../models/todo2.dart';
import '../widgets/dialogs.dart';
class TodoEdit extends StatefulWidget {
String? doctitle;
String? doctdescription;
String? docimage;
String? docid;
List? doctags;
TodoEdit({Key? key, this.doctitle, this.doctdescription, this.docimage, this.docid,this.doctags}) : super(key: key);
#override
_TodoEditState createState() => _TodoEditState();
}
class _TodoEditState extends State<TodoEdit> {
final _formKey = GlobalKey<FormState>();
final tcontroller = TextEditingController();
final dcontroller = TextEditingController();
final icontroller = TextEditingController();
var textEditingControllers = <TextEditingController>[];
//-----------------the list where the form is stored----------
var textformFields = <Widget>[];
void _addformWidget(controller) {
setState(() {
textformFields.add(tagForm(controller));
});
}
//------------------------------------------------------------------------
Widget tagForm(controller){
return TextFormField(
controller: controller,
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: "Tag",
labelStyle: TextStyle(color: Colors.white60),
fillColor: Colors.black,
filled: true,
suffixIcon: IconButton(
icon:Icon(Icons.delete, color: Colors.white,),
//--------------------- doesn't work?-------------------
onPressed: (){
setState(() {
textformFields.remove(tagForm(controller));
});
},
--------------------------------------------------------------
)
),
);
}
//-----------------------------------------------------------
#override
void initState() {
super.initState();
tcontroller.text = widget.doctitle.toString();
dcontroller.text = widget.doctdescription.toString();
icontroller.text = widget.docimage.toString();
widget.doctags?.forEach((element) {
var textEditingController = new TextEditingController(text: element);
textEditingControllers.add(textEditingController);
//return textformFields.add(tagForm(textEditingController)
return _addformWidget(textEditingController);
//);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[900],
appBar: AppBar(
actions: [
IconButton(onPressed: (){
showDialog(
barrierDismissible: false,
context: context,
builder: (context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
title: Text('Delete TODO'),
actions: [
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text('Delete'),
onPressed: () {
deleteData(widget.docid.toString(), context);
setState(() {
showSnackBar(context, 'todo "${widget.doctitle}" successfully deleted!');
});
},
),
],
);
},
);
},
icon: Icon(Icons.delete))
],
backgroundColor: Colors.grey[900],
title: Text("${widget.doctitle}"),
),
body: Container(
child: SafeArea(
child: Form(
key: _formKey,
child: Column(
children: [
SizedBox(height: 10),
TextFormField(
controller: tcontroller,
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: "Title",
labelStyle: TextStyle(color: Colors.white60),
fillColor: Colors.black,
filled: true,
),
),
SizedBox(height: 10),
TextFormField(
controller: dcontroller,
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: "Description",
labelStyle: TextStyle(color: Colors.white60),
fillColor: Colors.black,
filled: true,
),
),
SizedBox(height: 10),
TextFormField(
controller: icontroller,
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: "Image url",
labelStyle: TextStyle(color: Colors.white60),
fillColor: Colors.black,
filled: true,
),
),
SizedBox(height: 10),
Row(children: [
Text("Tags:", style:TextStyle(color: Colors.white)),
IconButton(onPressed: (){
var textEditingController = new TextEditingController(text: "tag");
textEditingControllers.add(textEditingController);
_addformWidget(textEditingController);
print(textformFields.length);
},
icon: Icon(Icons.add,color: Colors.white,),
)
],),
/*SingleChildScrollView(
child: new Column(
children: textformFields,
)
),*/
Expanded(
child: SizedBox(
height: 200.0,
child: ListView.builder(
itemCount: textformFields.length,
itemBuilder: (context,index) {
return textformFields[index];
}),
)
),
],
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
List<String> test = [];
textEditingControllers.forEach((element) {
test.add(element.text);
});
if(tcontroller == '' && dcontroller == '' && icontroller == ''){
print("not valid");
}else{
var todo = Todo2(
title: tcontroller.text,
description: dcontroller.text,
image: icontroller.text,
tags: test,
);
updateData(todo, widget.docid.toString(),context);
setState(() {
showSnackBar(context, 'todo ${widget.doctitle} successfully updated!');
});
}
},
child: Icon(Icons.update),
),
);
}
}
You can't remove anything from the list with objects from tagForm(controller), because these objects are newly created and therefore not the same as in the list (as long as the == operator is not overwritten)
If you still want to have the widgets in a list instead of just storing the controllers and without having to change much, you could remove the widgets like this:
onPressed: (){
setState(() {
controller.dispose();
textEditingControllers.remove(controller);
textformFields.removeWhere((w) => w.controller = controller));
});
},
and change the type of your List: var textformFields = <TextFormField>[]; and of the method TextFormField tagForm(controller).
In general, you can of course optimize the state management, but with this solution it should work for now.
Dont't store Widget, it is bad way. Insteads store there property, render by List then remove by index when you need.
ps: some code syntax can wrong, i write this on browser.
class _TodoEditState extends State<TodoEdit> {
...
var textformFields = <String>[];
...
void _addformWidget([String? initValue]) {
setState(() => textformFields.add(initValue ?? ""));
}
...
Widget tagForm(String value, void Function(String) onChange, void Function() onRemove){
var openEditor = () {
// Open dialog with text field to edit from [value] call onChange with
// new value
OpenDialog().then((newvalue) {
if(newvalue != null) onChange(newvalue);
}
};
var delete = () {
// Open confirm dialog then remove
OpenConfirmDialog("your message").then((continue) {
if(continue) onRemove();
});
};
return InkWell(
onTap: openEditor,
child: Text(value), // render your tag value
);
}
...
#override
void initState() {
...
textformFields = List.filled(widget.doctags ?? 0, ""); // or List.generate/map if you want replace by own value.
}
...
#override
Widget build(BuildContext context) {
...
ListView.builder(
itemCount: textformFields.length,
itemBuilder: (context,index) => tagForm(
textformFields[index],
(newvalue) => setState(() => textformFields[index] = newvalue),
() => setState(() => textformFields = textformFields..removeAt(index));,
),
),
...
);
}
I am trying to do collapse paneItem in navigationpane after a lot of searcb and i didn't found anything about that if anyone used fluent ui with flutter and know how to do that it will be nice
That is mycode:
import 'dart:ui';
import 'package:fluent_ui/fluent_ui.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return FluentApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
brightness: Brightness.dark,
),
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> {
int _counter = 0;
int _selectedindex = 0;
bool _visible = true;
TextEditingController search = TextEditingController();
final autoSuggestBox = TextEditingController();
final values = ['Blue', 'Green', 'Yellow', 'Red'];
String? comboBoxValue;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
void initState() {
search.text = 'Search';
super.initState();
}
#override
Widget build(BuildContext context) {
return NavigationView(
appBar: NavigationAppBar(
title: Text(widget.title),
),
pane: NavigationPane(
displayMode: PaneDisplayMode.compact,
onChanged: (newindex) {
setState(() {
_selectedindex = newindex;
});
},
footerItems: [
PaneItemSeparator(),
PaneItem(
icon: const Icon(FluentIcons.settings),
title: const Text('Settings'),
),
],
selected: _selectedindex,
autoSuggestBox: AutoSuggestBox(
controller: TextEditingController(),
placeholder: 'Search',
trailingIcon: Icon(FluentIcons.search),
items: const ['Item 1', 'Item 2', 'Item 3', 'Item 4'],
),
autoSuggestBoxReplacement: const Icon(FluentIcons.search),
items: [
PaneItem(
icon: const Icon(FluentIcons.settings),
title: const Text('page 0')),
PaneItemHeader(header: Text('data')),
PaneItem(
icon: const Icon(FluentIcons.settings),
title: const Text('page 1')),
]),
content: NavigationBody(index: _selectedindex, children: [
ScaffoldPage(
padding: EdgeInsets.only(top: 0),
header: _visible
? InfoBar(
title: const Text('Update available'),
content:
const Text('Restart the app to apply the latest update.'),
severity: InfoBarSeverity.info,
onClose: () {
setState(() => _visible = false);
})
: null,
content: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
child: AutoSuggestBox(
controller: autoSuggestBox,
items: const [
'Blue',
'Green',
'Red',
'Yellow',
'Grey',
],
onSelected: (text) {
print(text);
}),
),
SizedBox(
height: 20,
),
SizedBox(
width: 200,
child: Combobox<String>(
placeholder: Text('Selected list item'),
isExpanded: true,
items: values
.map((e) => ComboboxItem<String>(
value: e,
child: Text(e),
))
.toList(),
value: comboBoxValue,
onChanged: (value) {
// print(value);
if (value != null) setState(() => comboBoxValue = value);
},
),
),
SizedBox(
height: 20,
),
FilledButton(
style: ButtonStyle(
backgroundColor: ButtonState.all(Colors.blue)),
onPressed: () {
// showDialog(
// context: context,
// builder: (context) {
// return ContentDialog(
// title: Text('No WiFi connection'),
// content: Text('Check your connection and try again'),
// actions: [
// Button(
// child: Text('Ok'),
// onPressed: () {
// Navigator.pop(context);
// })
// ],
// );
// },
// );
},
child: const Icon(FluentIcons.add),
)
],
),
),
),
const ScaffoldPage(
header: PageHeader(
title: Text(
'Your Page 1',
textAlign: TextAlign.center,
)),
content: Center(child: Text('Page 1')),
),
const ScaffoldPage(
header: PageHeader(
title: Text(
'Your Page 2',
textAlign: TextAlign.center,
)),
content: Center(child: Text('Page 2')),
),
const ScaffoldPage(
header: PageHeader(
title: Text(
'Your Page 3',
textAlign: TextAlign.center,
)),
content: Center(child: Text('Page 3')),
),
]),
);
}
}
I am trying to do multi-level of paneItem in navigationpane in fluent ui in flutter but i don't know how to do that if anyone used fluent ui with flutter and know how to do that it will be nice
I trying to implement rows Search on Data Table on my flutter web app using TextEditingController. The data comes from The API as Json formte using Employee model.
I used FutureBuilder to get the data from the API. And i inserted Snapshot data into 'empList' as List. Also Created empsFiltered List to show search filtered data.
The issue is: Unable to show actual data in the datatable on startup. But the data is shown while searching and after clearing the searchtextfield.
I want to show the actual data on startup. And also the data should be shown as searched.
How to do this.
class EditorHome extends StatefulWidget {
const EditorHome({Key? key}) : super(key: key);
#override
_EditorHomeState createState() => _EditorHomeState();
}
class _EditorHomeState extends State<EditorHome> {
TextEditingController searchController = TextEditingController();
String _searchResult = '';
List empList = [];
List empsFiltered = [];
#override
void initState() {
super.initState();
empsFiltered = empList;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Table Example with search'),
),
body: SingleChildScrollView(
child: Column(
children: [
Card(
child: ListTile(
leading: const Icon(Icons.search),
title: TextField(
controller: searchController,
decoration: const InputDecoration(
hintText: 'Search', border: InputBorder.none),
onChanged: (value) {
setState(() {
_searchResult = value;
empsFiltered = empList
.where((e) =>
e.name.contains(_searchResult.toLowerCase()) ||
e.email.contains(_searchResult.toLowerCase()))
.toList();
});
}),
trailing: IconButton(
icon: const Icon(Icons.cancel),
onPressed: () {
setState(() {
searchController.clear();
_searchResult = '';
empsFiltered = empList;
});
},
),
),
),
FutureBuilder<List<Employees>>(
//initialData: const <Employees>[],
future: fetchResults(),
builder: (context, snapshot) {
if (snapshot.hasError ||
snapshot.data == null ||
snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
empList = snapshot.data!;
return DataTable(
headingTextStyle: const TextStyle(
fontWeight: FontWeight.bold, color: Colors.black),
headingRowHeight: 50,
showBottomBorder: true,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 1)),
columns: const [
DataColumn(label: SizedBox(width: 30, child: Text('ID'))),
DataColumn(
label: SizedBox(width: 100, child: Text('Name'))),
DataColumn(
label: SizedBox(width: 100, child: Text('Email'))),
],
rows: List.generate(
empsFiltered.length,
(index) {
var emp = empsFiltered[index];
return DataRow(cells: [
DataCell(
Text(emp.id.toString()),
),
DataCell(
Text(emp.name),
),
DataCell(
Text(emp.email),
),
]);
},
).toList(),
);
},
),
],
),
),
);
}
}
Below is my API:
Future<List<Employees>> fetchResults() async {
//List<Employees> _results = [];
Uri url = Uri.parse(" http:link ");
var response = await http.get(url);
var resultsJson = json.decode(response.body).cast<Map<String, dynamic>>();
List<Employees> emplist = await resultsJson
.map<Employees>((json) => Employees.fromJson(json))
.toList();
return emplist;
}
Posting here to add the code snippet:
You should create a state variable to contain the result of fetchResults, call fetchResults/set that variable in your initState. Use that new variable as future of the futureBuilder instead of the function call directly
class _EditorHomeState extends State<EditorHome> {
Future<List> futureList;
#override
void initState() {
super.initState();
futureList = fetchResults();
}
#override
Widget build(BuildContext context) {
return Scaffold(
...
FutureBuilder<List<Employees>>(
future: futureList,
builder: (context, snapshot) {
...
);
}
}
I got a solution. In this Case I used Two StatefulWidgets. One is for calling Future with fetchResults() and converted into a List, And second is for Table with Search filter, And called that first stateFulWidget List into a variable on second StatefulWidgets and set that List variable in initState as into empFiltered List. Working Fine.
Example Code:
class EditorHome extends StatefulWidget {
const EditorHome({Key? key}) : super(key: key);
#override
_EditorHomeState createState() => _EditorHomeState();
}
class _EditorHomeState extends State<EditorHome> {
List empList = [];
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Employees>>(
future: fetchResults(),
builder: (context, snapshot) {
if (snapshot.hasError ||
snapshot.data == null ||
snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
empList = snapshot.data!;
return TableSec(fempList: empList);
});
}
}
class TableSec extends StatefulWidget {
final List fempList;
const TableSec({Key? key, required this.fempList}) : super(key: key);
#override
_TableSecState createState() => _TableSecState();
}
class _TableSecState extends State<TableSec> {
late List empList = widget.fempList;
List empsFiltered = [];
TextEditingController searchController = TextEditingController();
String _searchResult = '';
#override
void initState() {
super.initState();
empsFiltered = empList;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Table"),
),
body: SingleChildScrollView(
child: Column(
children: [
Card(
child: ListTile(
leading: const Icon(Icons.search),
title: TextField(
controller: searchController,
decoration: const InputDecoration(
hintText: 'Search', border: InputBorder.none),
onChanged: (value) {
setState(() {
_searchResult = value;
empsFiltered = empList
.where((e) =>
e.name.contains(_searchResult.toLowerCase()) ||
e.email.contains(_searchResult.toLowerCase()))
.toList();
//print(_searchResult);
});
}),
trailing: IconButton(
icon: const Icon(Icons.cancel),
onPressed: () {
setState(() {
searchController.clear();
_searchResult = '';
empsFiltered = empList;
});
},
),
),
),
DataTable(
headingTextStyle: const TextStyle(
fontWeight: FontWeight.bold, color: Colors.black),
headingRowHeight: 50,
showBottomBorder: true,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 1)),
columns: const [
DataColumn(label: SizedBox(width: 30, child: Text('ID'))),
DataColumn(label: SizedBox(width: 100, child: Text('Name'))),
DataColumn(label: SizedBox(width: 100, child: Text('Email'))),
],
rows: List.generate(
empsFiltered.length,
(index) {
var emp = empsFiltered[index];
return DataRow(cells: [
DataCell(
Text(emp.id.toString()),
),
DataCell(
Text(emp.name),
),
DataCell(
Text(emp.email),
),
]);
},
).toList(),
)
],
),
),
);
}
}
I'm trying to sort the data in the Flutter Data Table, but no matter what I try, either it does nothing or it returns this error: The method 'sort' was called on null. Receiver: null Tried calling: sort(Closure: (VehicleData, VehicleData) => int). I've tried many options I read online, but none of them seems to work, so it's got to be somewhere in my code. Here it is:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/vehicle.dart';
import 'screens/vehicle_details_screen.dart';
import 'services/vehicle_api.dart';
import 'screens/edit_screen.dart';
import 'models/vehicle_data_provider.dart';
import 'package:http/http.dart' as http;
class VehicleList extends StatefulWidget {
#override
_VehicleList createState() => _VehicleList();
}
class _VehicleList extends State<VehicleList> {
bool _sortNameAsc = true;
bool _sortAsc = false;
int _sortColumnIndex;
List<VehicleData> _persons;
_getPosts() async {
HomePageProvider provider =
Provider.of<HomePageProvider>(context, listen: false);
var postsResponse = await fetchVehicles();
if (postsResponse.isSuccessful) {
provider.setPostsList(postsResponse.data, notify: false);
} else {
provider.mergePostsList(postsResponse.data, notify: false);
}
provider.setIsHomePageProcessing(false);
}
#override
void initState() {
_getPosts();
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Consumer<HomePageProvider>(
builder: (context, vehicleData, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
SizedBox(
height: 12.0,
),
Container(
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.all(
Radius.circular(12.0),
),
),
child: SingleChildScrollView(
child: DataTable(
sortColumnIndex: _sortColumnIndex,
sortAscending: _sortAsc,
columnSpacing: 30,
columns: <DataColumn>[
DataColumn(
numeric: false,
onSort: (columnIndex, sortAscending) {
setState(() {
if (columnIndex == _sortColumnIndex) {
_sortAsc = _sortNameAsc = sortAscending;
} else {
_sortColumnIndex = columnIndex;
_sortAsc = _sortNameAsc;
}
_persons.sort((a, b) =>
a.friendlyName.compareTo(b.friendlyName));
if (!sortAscending) {
_persons = _persons.toList();
}
});
},
label: Text(
'Friendly Name',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
DataColumn(
label: Text(
'Licence Plate',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
DataColumn(
label: Text(
'Delete',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
],
rows: List.generate(
vehicleData.postsList.length,
(index) {
VehicleData post = vehicleData.getPostByIndex(index);
return DataRow(
cells: <DataCell>[
DataCell(
Text('${post.friendlyName}'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
VehicleDetailsScreen(
color: post.color,
friendlyName:
post.friendlyName,
licencePlate:
post.licencePlate,
)));
},
),
DataCell(
Text('${post.licencePlate}'),
),
DataCell(
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
vehicleData.deletePost(post);
},
),
),
],
);
},
),
),
),
),
],
);
},
),
],
);
}
}
I've tried putting the _sortASc in the initState and set it as false, but doesn't seem to do the trick. Any help is appreciated!
Your _persons list is null. You can initialize it. And check if null before sorting.
List<VehicleData> _persons = List<VehicleData>();
// ...
if(_persons != null) {
_persons.sort((a, b) =>
a.friendlyName.compareTo(b.friendlyName));
if (!sortAscending) {
_persons = _persons.toList();
}
}