Render DataTable rows somoorhly on 1 page in Flutter - flutter

I'm working on a desktop application project using flutter. Most of the pages contain a table to show data. my problem is, when I use the regular DataTable the app hangs for 1 to 3 seconds to render and draw all the rows of the table in one page. On the other hand, the PaginatedDataTable renders the table smoothly but a specific number of rows per page. My question is, is there any way to load all the rows smoothly in one page using the regular DataTable?
Here is the page code
I noticed that method 1 is slightly faster than method 2
import 'package:flutter/material.dart';
import 'package:ghiyar/DataModels/product_part.dart';
import 'package:ghiyar/CoreControllers/database_controller.dart';
class PartsPage extends StatefulWidget {
const PartsPage({Key? key}) : super(key: key);
#override
_PartsPageState createState() => _PartsPageState();
}
class _PartsPageState extends State<PartsPage>{
Future<List<ProductPart>>? _parts;
#override
void initState(){
super.initState();
_getTripsFromDB();
}
#override
void dispose() {
super.dispose();
}
Future _getTripsFromDB() async{
List<ProductPart> dbpps = await DatabaseController.getProductParts();
setState(() { _parts = Future.value(dbpps); });
}
////////////////// method 1 //////////////////
Widget _getPartsList(List<ProductPart>? pps){
return SizedBox(
width: double.infinity,
child: DataTable(
columns: const <DataColumn>[
DataColumn(
label: Text('Number'),
),
DataColumn(
label: Text('Number'),
),
DataColumn(
label: Text('Number'),
),
DataColumn(
label: Text('Number'),
),
],
rows: pps == null ? [] : pps.map((e) => DataRow(
cells: <DataCell>[
DataCell(Text(e.number)),
DataCell(Text(e.number)),
DataCell(Text(e.number)),
DataCell(Text(e.number)),
],
selected: true,
)).toList()
),
);
}
//////////////////// method 2 /////////////////////////
Widget _getPartsList2(List<ProductPart>? pps){
return SizedBox(
width: double.infinity,
child: DataTable(
columns: const <DataColumn>[
DataColumn(
label: Text('Number'),
),
DataColumn(
label: Text('Number'),
),
DataColumn(
label: Text('Number'),
),
DataColumn(
label: Text('Number'),
),
],
rows: List<DataRow>.generate(
pps != null ? pps.length : 0,
(index) => DataRow(
color: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
// All rows will have the same selected color.
if (states.contains(MaterialState.selected)) {
return Theme.of(context).colorScheme.primary.withOpacity(0.08);
}
// Even rows will have a grey color.
if (index.isEven) {
return Colors.grey.withOpacity(0.3);
}
return null; // Use default value for other states and odd rows.
}),
cells: <DataCell>[
DataCell(Text(pps![index].number)),
DataCell(Text(pps![index].number)),
DataCell(Text(pps![index].number)),
DataCell(Text(pps![index].number)),
],
selected: true,
onSelectChanged: (bool? value) {
setState(() {
//selected[index] = value!;
});
},
)
),
),
);
}
#override
Widget build(BuildContext context) {
return Material(
child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 10.00, horizontal: 10.00),
child: SingleChildScrollView(
child: Center(
child: Text('control'),
),
),
),
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.00, horizontal: 0.00),
child: FutureBuilder<List<ProductPart>>(
future: _parts,
builder: (context, snapshot){
if (snapshot.hasError) {
return const Center(
child: Text('Unexpected error has occurred! Please refresh the page'),
);
} else if (snapshot.hasData) {
return _getPartsList(snapshot.data);
} else {
return const Center(
child: Center(child: CircularProgressIndicator(color: Color(0xff87c94d), strokeWidth: 10),)
);
}
},
),
),
)
)
],
),
);
}
}

Related

flutter cubits display data on a DataTable depending on the data from other DataTables

I'm new to flutter, flutter_bloc library particularly with cubits.
I'm trying to populate dynamic data from 3 different api calls to DataTables using cubits.
The pattern is as follows:
Gather the parameters needed for 2nd table from 1st Table, after 1st Table is loaded (done)
Gather parameters needed for 3rd table from the 2nd table (done)
The problem is triggering the cubit with methods don't populate the DataTables
context.read<ReceivingListDetailCubit>().getRcvwork8010F_20Q(datamap10Q['rcv_dt'], datamap10Q['rcv_seq']);
Currently I temporarily place this code inside BlocBuilder for the 1st Table which is not quite right.
Where should I placed the context.read<> code?
I tried putting it inside the listener of BlocConsumer but it seems it doesn't execute it
I tried my best to read and understand about cubits unfortunately, and due to lack of examples regarding dynamic data, I was unable to make this work.
Here are the codes
eighty_ten_tablet_pg2.dart
class EightyTenTabletPg2 extends StatefulWidget {
late String recvNo;
EightyTenTabletPg2({Key? key, required this.recvNo}) : super(key: key);
#override
_EightyTenTabletPg2State createState() => _EightyTenTabletPg2State();
}
class _EightyTenTabletPg2State extends State<EightyTenTabletPg2> {
var datamap10Q = {};
var datamap20Q = {};
var combinedMap = {};
var dataList = [];
void handleSelectedIndex(int val) {
setState(() {
selectedIndex = val;
print('selectedIndex is $val');
});
}
//Table 1 Columns
List<DataColumn> _createColumns_10Q() {
return [
const DataColumn(label: Text('Date Received')),
const DataColumn(numeric: truelabel: Text('Queue')),
const DataColumn(label: Text('Receiving Status')),
const DataColumn(label: Text('Supplicer code')),
const DataColumn(label: Text('Account Name')),
const DataColumn(label: Text('Receiving Type')),
const DataColumn(numeric: true,label: Text('Count')),
];
}
//Table 1 Rows
List<DataRow> _createRows_10Q(receivingList) {
List<DataRow> rcvRows = [];
if(receivingList.length > 0) {
for(int x=0; x < receivingList.length; x++ ) {
datamap10Q['rcv_dt'] = receivingList[x].rcv_dt;
datamap10Q['rcv_seq'] = receivingList[x].rcv_seq.toString();
print("datamap10Q: ${datamap10Q['rcv_dt']} ${datamap10Q['rcv_seq']}");
rcvRows.add(
DataRow(
onSelectChanged: (val) {
handleSelectedIndex((x+1));
},
cells: [
DataCell(Text(receivingList[x].rcv_dt)),
DataCell(Text(receivingList[x].rcv_seq.toString())),
DataCell(Text(receivingList[x].rcv_status_nm)),
DataCell(Text(receivingList[x].cust_cd)),
DataCell(Text(receivingList[x].cust_nm)),
DataCell(Text(receivingList[x].rcv_type_nm)),
DataCell(Text(receivingList[x].item_cnt.toString())),
]
)
);
}
} else {
rcvRows.add(
const DataRow(
cells: <DataCell>[
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('No rows to show.')),
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
DataCellText('')),
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
]
)
);
}
return rcvRows;
}
//Table 1
DataTable _createDataTable_10Q(state) {
return DataTable(
columns: _createColumns_10Q(),
rows: _createRows_10Q(state.rcvLists),
);
}
//Table 2 Columns
List<DataColumn> _createColumns_20Q() {
return [
const DataColumn(numeric: true,label: Text('No')),
const DataColumn(numeric: true,label: Text('Queue')),
const DataColumn(label: Text('Receiving Status')),
const DataColumn(label: Text('Item Code')),
const DataColumn(label: Text('Item Name')),
const DataColumn(label: Text('Unit')),
const DataColumn(label: Text('Unit Mng')),
const DataColumn(label: Text('Expiration Type')),
const DataColumn(numeric: true,label: Text('Shelf Life')),
const DataColumn(numeric: true,label: Text('Order Qty')),
const DataColumn(numeric: true,label: Text('Received Qty')),
const DataColumn(numeric: true,label: Text('Inspected Qty')),
const DataColumn(label: Text('Storage Loc')),
];
}
//Table 2
List<DataRow> _createRows_20Q(rcvListDetail) {
List<DataRow> rcvDetailRows = [];
if(rcvListDetail.length > 0) {
for(int x=0; x < rcvListDetail.length; x++ ) {
// I tried to store certain columns for calling on a cubit's method later
datamap20Q['dtlSeq'] = rcvListDetail[x].dtl_seq.toString();
combinedMap['rcv_dt'] = datamap10Q['rcv_dt'];
combinedMap['rcv_seq'] = datamap10Q['rcv_seq'];
combinedMap['dtlSeq'] = datamap20Q['dtlSeq'];
dataList.add(combinedMap);
print('combinedMap: $combinedMap');
print('dataList: $dataList');
rcvDetailRows.add(
DataRow(
onSelectChanged: (val) {
handleSelectedIndex((x+1));
},
cells: [
DataCell(Text('${x+1}')),
DataCell(Text(rcvListDetail[x].dtl_seq.toString())),
DataCell(Text(rcvListDetail[x].rcv_status_nm)),
DataCell(Text(rcvListDetail[x].item_cd)),
DataCell(Text(rcvListDetail[x].item_nm)),
DataCell(Text(rcvListDetail[x].item_unit)),
DataCell(Text(rcvListDetail[x].item_mng_unit_nm)),
DataCell(Text(rcvListDetail[x].vld_mng_type_nm)),
DataCell(Text(rcvListDetail[x].vld_day.toString())),
DataCell(Text(rcvListDetail[x].ord_qty.toString())),
DataCell(Text(rcvListDetail[x].rcv_qty.toString())),
DataCell(Text(rcvListDetail[x].insp_qty.toString())),
DataCell(Text(rcvListDetail[x].keep_loc.toString())),
]
)
);
}
} else {
rcvDetailRows.add(
const DataRow(
cells: <DataCell>[
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('No rows to show.')),
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
DataCellText('')),
DataCell(Text('')),
DataCell(Text('')),
DataCell(Text('')),
]
)
);
}
return rcvDetailRows;
}
// Table 2
DataTable _createDataTable_20Q(state) {
return DataTable(
columns: _createColumns_20Q(),
rows: _createRows_20Q(state.rcvListDetail),
);
}
// Table 3
List<DataColumn> _createLotWarehousingColumns() {
return [
const DataColumn(numeric: true,label: Text('No')),
const DataColumn(numeric: true,label: Text('Queue')),
const DataColumn(label: Text('Expiration Date')),
const DataColumn(label: Text('LOT')),
const DataColumn(numeric: truelabel: Text('Received Qty')),
];
}
// Table 3
List<DataRow> _createLotWarehousingRows(lotWarehousingLists) {
int selectedIndex = -1;
List<DataRow> lotWarehousingRows = [];
for(int x=0; x < lotWarehousingLists.length; x++ ) {
lotWarehousingRows.add(
DataRow(
selected: x == selectedIndex,
onSelectChanged: (val) {
// handleSelectedIndex((x+1));
setState(() {
selectedIndex = x;
});
},
cells: [
DataCell(Text('${x+1}')),
DataCell(Text(lotWarehousingLists[x].lot_seq.toString())),
DataCell(Text(lotWarehousingLists[x].vld_dt)),
DataCell(Text(lotWarehousingLists[x].lot_no)),
DataCell(Text(lotWarehousingLists[x].rcv_qty.toString())),
]
)
);
}
return lotWarehousingRows;
}
// Table 3
DataTable _createLotWarehousingDataTable(state) {
return DataTable(
columns: _createLotWarehousingColumns(),
rows: _createLotWarehousingRows(state.lotWarehousingLists),
);
}
Widget buildCommonBtn({
required String text,
required String color,
VoidCallback? onTap
}) {
MaterialColor appliedColor = Colors.blue;
if(color == 'red') {
appliedColor = Colors.red;
}
return SizedBox(
height: 50,
child: ElevatedButton(
child: Text(
text,
style: const TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.bold,
),
),
style: ElevatedButton.styleFrom(
primary: appliedColor,
),
onPressed: onTap,
),
);
}
Widget buildDatePicker({
VoidCallback? onTap
}) {
return SizedBox(
width: 135,
child: TextField(
controller: managedDateController,
style: const TextStyle(
fontSize: 20.0,
),
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
onTap: () async {
var startDate = await showDatePicker(
context: context,
initialDate:DateTime.now(),
firstDate:DateTime(1900),
lastDate: DateTime(2100));
managedDateController.text = startDate.toString().substring(0,10);
},
),
);
}
void selectedItem(BuildContext context, int index) {
switch (index) {
case 4:
print('Index is: $index');
Navigator.pushNamed(context, route.eightyTenAddLot);
break;
default:
throw('The route does not exist yet.');
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: const Text('8010 - Receipt Inspection Registration'),
leading: IconButton(icon: const Icon(Icons.arrow_back),
onPressed:() => Navigator.pop(context, false),
)
),
body: MultiBlocProvider(
providers: [
BlocProvider<ReceivingListsCubit>(
create: (context) => ReceivingListsCubit()..getRcvwork8010F_10Q(widget.recvNo),
),
//..getRcvwork8010F_20Q('20211230', '2')
BlocProvider<ReceivingListDetailCubit>(
create: (context) => ReceivingListDetailCubit()..getRcvwork8010F_20Q('', ''),
),
//..getRcvWork8010F_30Q('20211230', '2', '1')
BlocProvider<LotWarehousingListsCubit>(
create: (context) => LotWarehousingListsCubit()..getRcvWork8010F_30Q('', '', ''),
),
],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Builder(
builder: (context) {
return SingleChildScrollView(
child: BlocConsumer<ReceivingListsCubit, ReceivingListsStates>(
listener: (context, state) {
if (state is rlLoadedState) {
context.read<ReceivingListDetailCubit>().getRcvwork8010F_20Q(datamap10Q['rcv_dt'], datamap10Q['rcv_seq']);
print("10Q listener\ndatamap10Q['rcv_dt']: ${datamap10Q['rcv_dt']}");
// I tried here but the datamap10Q values are null I thought the listener will get called during the rlLoadedState state?
//context.read<ReceivingListsCubit>().getRcvwork8010F_10Q(widget.recvNo);
}
},
builder: (context, state) {
// putting this context.read code works it shows the data for Table 2 but this isn't supposed to be here
context.read<ReceivingListDetailCubit>().getRcvwork8010F_20Q(datamap10Q['rcv_dt'], datamap10Q['rcv_seq']);
if(state is rlLoadingState) {
return const CustomProgressIndicator();
} else if(state is rlLoadedState) {
return _createDataTable_10Q(state);
} else {
return Text('No Rows to show');
}
},
)
);
}
),
const SizedBox(
height: 20.0,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 20.0),
child: Row(
children: [
// Star
Expanded(
child: Row(
children: const [
Icon(
Icons.star,
color: Colors.red
),
Text(
'Items Ordered To Be Received',
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
buildCommonBtn(
text: 'Issue LOT Label',
color: '',
onTap: () => selectedItem(context, 1)
),
const SizedBox(
width: 80.0,
),
buildCommonBtn(
text: 'Confirmation',
color: '',
onTap: () => selectedItem(context, 1)
),
const SizedBox(
width: 30.0,
),
buildCommonBtn(
text: 'Insp Cancel',
color: 'red',
onTap: () => selectedItem(context, 1)
),
],
),
),
],
),
),
const SizedBox(
height: 10.0,
),
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 20.0),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: BlocBuilder<ReceivingListDetailCubit, ReceivingListDetailStates>(
// listener: (context, state) {
// if(state is rldLoadedState) {
// print('listener 20Q');
// print("datamap10Q['rcv_dt']: ${datamap10Q['rcv_dt']}");
//
// }
// },
builder: (context, state) {
if(state is rldLoadingState) {
return const CustomProgressIndicator();
} else if(state is rldLoadedState) {
return _createDataTable_20Q(state);
} else {
return Text('No Rows to show');
}
}
),
),
),
),
const SizedBox(
height: 15.0,
),
Column(children: [
SizedBox(
width: 650,
child: Row(
children: [
Expanded(
child: Row(
children: const [
Icon(
Icons.star,
color: Colors.red
),
Text(
'LOT Warehousing',
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
buildCommonBtn(
text: 'Add LOT',
color: '',
onTap: () => selectedItem(context, 4)
),
const SizedBox(
width: 25.0
),
buildCommonBtn(
text: 'Delete LOT',
color: 'red',
onTap: () => selectedItem(context, 6)
),
],
),
),
],
),
),
const SizedBox(
height: 10.0,
),
Padding(
padding: const EdgeInsets.only(left: 20.0),
child: BlocBuilder<LotWarehousingListsCubit, LotWarehousingListsStates>(
builder: (context, state) {
if(state is lwlLoadingState) {
return const CustomProgressIndicator();
} else if(state is lwlLoadedState) {
return _createLotWarehousingDataTable(state);
} else {
return const Text('No Rows to show');
}
}
),
),
],
)
],
),
),
),
);
}
}

Callback from child to parent in flutter

I am Developing Cart Page for e com application. backend is firebase real time database. In cart Page Price Details is calculating when the page load. But when user change the quantity of cart item, It is going to update the cart. But the problem is Cant Calculate the Price Details On this time. When I reopen the page, Price Details is Calculating According to new Cart Quantity. But How can I can do that without re opening the Page
1.This is my cart_page.dart file
//this cart is show when user select my cart From the navigation drawer
import 'package:flutter/material.dart';
import 'package:buy_app_flutter/main.dart';
import 'package:buy_app_flutter/pages/notification_page.dart';
import 'package:buy_app_flutter/pages/search_page.dart';
import 'package:buy_app_flutter/componets/cart_products.dart';
import 'BuyNowDeliveryDetailsPage.dart';
import 'HomePage.dart';
class CartPage extends StatefulWidget {
#override
_CartPageState createState() => _CartPageState();
}
class _CartPageState extends State<CartPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: new DrawerCodeOnly(),
appBar: new AppBar(
elevation: 0.1,
backgroundColor: Colors.orangeAccent,
title: Text("My Cart"),
actions:<Widget> [
new IconButton(icon: Icon(Icons.search,color: Colors.white,), onPressed: (){
showSearch(context: context, delegate: DataSearch());
} ),
new IconButton(icon: Icon(Icons.notifications,color: Colors.white,), onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => new NotificationPage() ));
} ),
],
),
body:new Cart_products(),
bottomNavigationBar:new Container(
color: Colors.white,
child: Row(
children:<Widget> [
Expanded(child: ListTile(
title: new Text("Total:"),
subtitle:new Text("Rs 00.00") ,
)),
Expanded(
child: new MaterialButton(onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => new BuyNowDeliveryDetailsActivity() ));
},
child: new Text("Check Out"),
color: Colors.orangeAccent,
)
)
],
),
) ,
);
}
}
2.This is cart_procucts.dart file
import 'package:buy_app_flutter/DBModels/CartModel.dart';
import 'package:buy_app_flutter/DBModels/ProductModel.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class Cart_products extends StatefulWidget {
#override
_Cart_productsState createState() => _Cart_productsState();
}
class _Cart_productsState extends State<Cart_products> {
//firebase database reference to cart===============================
final Cartref= FirebaseDatabase.instance.reference().child("UserData").child(FirebaseAuth.instance.currentUser.uid).child("Cart");
List<CartModel> Products_on_the_cart=List();
///=============================================firebase end
bool _progressController=true;
//variables to cal total
int TotalItemPrice=0;
int SavedAmount=0;
int DeliveryPrice=0;
int TotalAmount=0;
#override
initState(){
_getCartList(); //call function to get cart from firebase
super.initState();
}
///display the cart Items one by one===============================
#override
Widget build(BuildContext context) {
if(_progressController==true) //reading database
{
return Center(child: new CircularProgressIndicator());
}
else{ //db read end=========
if(Products_on_the_cart.isNotEmpty)
{
_calTotalPrice();
//===============================================if not empty the cart
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListView.separated(
itemBuilder: (context, index){
return Single_cart_product(
cart_product_key: Products_on_the_cart[index].productKey,
cart_product_name: Products_on_the_cart[index].ProductTitle,
cart_prod_picture: Products_on_the_cart[index].Img,
cart_prod_price: Products_on_the_cart[index].Price,
cart_prod_cutted_price: Products_on_the_cart[index].CuttedPrice,
cart_prod_qty: Products_on_the_cart[index].qty,
cart_product_status: Products_on_the_cart[index].Status,
);
},
//end of item builder
separatorBuilder:(context,index){
return Row();
},
itemCount: Products_on_the_cart.length,
shrinkWrap: true,
),
Divider(),
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text("PRICE DETAILS",style: TextStyle(fontWeight: FontWeight.bold,color: Colors.grey),),
),
Divider(),
///start of items total price row===========================
Padding(
padding: const EdgeInsets.only(left: 8.0,right: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("price (${Products_on_the_cart.length} items)",style: TextStyle(fontWeight: FontWeight.normal,color: Colors.black),),
Text("Rs ${TotalItemPrice}/-",style: TextStyle(fontWeight: FontWeight.normal,color: Colors.black),),
],
),
),
///end of items total price row============================
///start of delivery price row==================================
Padding(
padding: const EdgeInsets.only(left: 8.0,top: 8.0,right: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Delivery",style: TextStyle(fontWeight: FontWeight.normal,color: Colors.black),),
Text("Rs ${DeliveryPrice}/-",style: TextStyle(fontWeight: FontWeight.normal,color: Colors.green),),
],
),
),
///end of delivery price row============================
Divider(),
///Start of total amount row==================================
Padding(
padding: const EdgeInsets.only(left: 8.0,right: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Total Amount",style: TextStyle(fontWeight: FontWeight.bold,color: Colors.black),),
Text("Rs ${TotalAmount}/-",style: TextStyle(fontWeight: FontWeight.bold,color: Colors.black),),
],
),
),
///end of total amount row===========================================
Divider(),
Padding(
padding: const EdgeInsets.only(left: 8.0,right: 8.0),
child: Text("You saved Rs ${SavedAmount}/- on this order",style: TextStyle(fontWeight: FontWeight.normal,color: Colors.green),),
),
],
);
//end of if not empty the cart===========================================
}
else
{
return Center(child: Text("Your cart is empty !"));
}
}
}
///end of displaying cart items one by one====================================
_getCartList()
async {
//get cart data from firebase==========
try{
await Cartref.once().then((DataSnapshot snapshot) {
var data = snapshot.value;
if(data==null) //if the no cart items found hide progress
{
setState(() {
_progressController=false;
});
}
Products_on_the_cart.clear();
data.forEach((key,value)
async {
CartModel model = new CartModel(
date: value["date"],
time: value["time"],
productKey: value["productKey"],
qty: value["qty"],
);
//use Pref(product ref) to get details from products record
try{
//Product details ref and list
final Pref= FirebaseDatabase.instance.reference().child("Product");
List<ProductModel> product =List();
//====================================
await Pref.child(model.productKey).once().then((DataSnapshot snap) {
// print('Product : ${snap.value}');
model.ProductTitle=snap.value["ProductTitle"];
model.Img=snap.value["img1"];
model.Price=snap.value["Price"];
model.DPrice=snap.value["DPrice"];
model.CuttedPrice=snap.value["CuttedPrice"];
model.Status=snap.value["Status"];
});
}
catch(e)
{
print("Error In try block for get data from real time db ");
print(e);
}
finally
{
setState(() {
_progressController=false;
});
}
//========================================================end of get product details
setState(() {
Products_on_the_cart.add(model);
});
}
);
});
}
catch(e)
{
print(e);
}
// end of get cart data from firebase
}
///function for calculate total price
_calTotalPrice(){
TotalItemPrice=0;
DeliveryPrice=0;
TotalAmount=0;
SavedAmount=0;
Products_on_the_cart.forEach((item){
//set total item price===========
TotalItemPrice=TotalItemPrice+int.parse(item.Price)*int.parse(item.qty);
//set delivery price==========
if (item.DPrice=="FREE")
{
//nothing
}
else
{
if (int.parse(item.DPrice)>DeliveryPrice)
{
DeliveryPrice=int.parse(item.DPrice);
}
}
//set Total amount=======
TotalAmount=TotalItemPrice+DeliveryPrice;
//set saved amount
SavedAmount=SavedAmount + (int.parse(item.CuttedPrice)-int.parse(item.Price))*int.parse(item.qty);
});
}
/// end of calculate total price function
}
///from here design the cart item display ==========================================
class Single_cart_product extends StatefulWidget {
final cart_product_key;
final cart_product_name;
final cart_prod_picture;
final cart_prod_price;
final cart_prod_cutted_price;
String cart_prod_qty;
final cart_product_status;
Single_cart_product({
this.cart_product_key,
this.cart_product_name,
this.cart_prod_picture,
this.cart_prod_price,
this.cart_prod_cutted_price,
this.cart_prod_qty,
this.cart_product_status,
});
#override
_Single_cart_productState createState() => _Single_cart_productState();
}
class _Single_cart_productState extends State<Single_cart_product> {
///==========================start of Cart Item Display Design==================================================================
#override
Widget build(BuildContext context) {
var cartQty=int.parse(widget.cart_prod_qty);
return Card(
child: Column(
children: [
ListTile(
///===============START LEADING SECTION ===============================
leading: FadeInImage.assetNetwork(
placeholder: 'assets/close_box.png',
image:widget.cart_prod_picture,
height: 80.0,
width: 80.0,
fit: BoxFit.contain,
),
///=======END OF LEADING SECTION=======================================
/// =========== TITLE SECTION =========================================
title: new Text(widget.cart_product_name),
///====END OF TITLE SECTION============================================
///========== SUBTITLE SECTION ==================================
subtitle: new Column(
children:<Widget> [
//Row inside the column
new Row(
children: <Widget> [
// =============this section is for the Product price =====
new Container(
alignment: Alignment.topLeft,
child: new Text("\Rs.${widget.cart_prod_price}",
style: TextStyle(
fontSize:16.0,
fontWeight: FontWeight.bold,
color: Colors.black
),
),
),
SizedBox(width: 4,),
//==========this section is for the cutted price of the product=========
Padding(
padding: const EdgeInsets.all(5.0),
child: new Text("\Rs.${widget.cart_prod_cutted_price}",
style: TextStyle(
fontSize:16.0,
fontWeight: FontWeight.normal,
decoration: TextDecoration.lineThrough,
color: Colors.grey
),
),
),
//end of cutted price of the product
],
),
],
),
///=====END OF SUBTITLE SECTION============================================
///======START OF TRAILING SECTION==========================================
trailing:
Column(
children:
<Widget>[
Expanded(child: new IconButton(
padding: const EdgeInsets.all(0.0),
icon: Icon(Icons.arrow_drop_up), onPressed: (){
cartQty++;
print(cartQty);
_updateCartQty(widget.cart_product_key,cartQty.toString()); //call to update cart qty
setState(() {
widget.cart_prod_qty=cartQty.toString();
});
})),
Padding(
padding: const EdgeInsets.all(0.0),
child: new Text(cartQty.toString()),
),
Expanded(child: new IconButton(
padding: const EdgeInsets.all(0.0),
icon: Icon(Icons.arrow_drop_down), onPressed: (){
if(cartQty>1)
{
cartQty--;
print(cartQty);
_updateCartQty(widget.cart_product_key,cartQty.toString()); //call to update cart qty
setState(() {
widget.cart_prod_qty=cartQty.toString();
});
}
})),
],
),
///==END OF TRAILING======================================================================
),
//REMOVE BTN AND STATUS
Row(
children: [
new IconButton(
icon: Icon(Icons.delete),
color:Colors.red ,
onPressed: (){}),
Padding(
padding: const EdgeInsets.all(4.0),
child: new Text(widget.cart_product_status,style: TextStyle(color: Colors.orangeAccent)),
),
],
)
//END OF REMOVE BTN SND STATUS
],
),
);
}
///==========================end of Cart Item Display Design==================================================================
/// function for update cart qty
_updateCartQty(String Pkey,String Qty){
try {
final Cartref = FirebaseDatabase.instance.reference()
.child("UserData")
.child(FirebaseAuth.instance.currentUser.uid)
.child("Cart");
Cartref.child(Pkey).child("qty").set(Qty);
}
catch(Ex)
{
Fluttertoast.showToast(
msg: Ex.message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
}
}
}
3.After that I need to view the calculated total amount in Bottom of the cart page
Updating product quantity value in Cart_products class:
You need to create a variable final ValueChanged<int> onProdQty; in Single_cart_product class and then where the quantity value changes use the callback like this widget.onProdQty?.call(here Pass the new quantity value). Also one last step is that in class Cart_products where you are calling Single_cart_product() add this in argument onProdQty: (value) => setState(() { Products_on_the_cart[index].qty = value; }),. By doing this whenever the callback is called the setstate would run in Cart_products class and wherever Products_on_the_cart[index].qty is used it would get updated.
Updating product quantity value in CartPage class:
Note: I'm not sure if these steps would work corectly as I have limited information about your project. But you will surely learn how to pass data around using callbacks:
Follow steps of Updating product quantity value in Cart_products
class and then continue.
Add final ValueChanged<int> onProdQty; in Cart_products class and
then change Single_cart_product() argument in class Cart_products
like this:
Single_cart_product(onProdQty: (value) => setState((){
Products_on_the_cart[index].qty = value;
widget.onProdQty?.call(value); //add this
}),)
Then add List<CartModel> Products_on_the_cart=List(); in the
_CartPageState class in cart_page.dart and at last in the body: Cart_products() make these changes:
Cart_products(onProdQty: (value) => setState((){
Products_on_the_cart[index].qty = value;
}),)
Now use Products_on_the_cart[index].qty in bottomNavigationBar.
Please let me know if you have any further queries.

Flutter: How to keep list data inside table even after switching screens back and forth?

I have a form (second screen) which is used for CRUD. When i add data, it is saved to list view as you can see on the table.
The list view is passed to (first screen) where i can iterate and see the list data with updated content.
However, when i click on go to second screen, the list view data disappears. The given 3 lists are hard coded for testing purpose.
Now, my question is that, How can i keep the data in the table and not disappear, even if i change screen back and forth multiple times. My code is as below: -
**
Main.dart File
**
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _userInformation = 'No information yet';
void languageInformation() async {
final language = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Episode5(),
),
);
updateLanguageInformation(language);
}
void updateLanguageInformation(List<User> userList) {
for (var i = 0; i <= userList.length; i++) {
for (var name in userList) {
print("Name: " + name.name[i] + " Email: " + name.email[i]);
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Testing List View Data From second page to first page"),
),
body: Column(
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(_userInformation),
],
),
SizedBox(
height: 10.0,
),
ElevatedButton(
onPressed: () {
languageInformation();
},
child: Text("Go to Form"),
),
],
),
);
}
}
2. Model.dart File:
class User {
String name;
String email;
User({this.name, this.email});
}
3. Episode5 File
class Episode5 extends StatefulWidget {
#override
_Episode5State createState() => _Episode5State();
}
class _Episode5State extends State<Episode5> {
TextEditingController nameController = TextEditingController();
TextEditingController emailController = TextEditingController();
final form = GlobalKey<FormState>();
static var _focusNode = new FocusNode();
bool update = false;
int currentIndex = 0;
List<User> userList = [
User(name: "a", email: "a"),
User(name: "d", email: "b"),
User(name: "c", email: "c"),
];
#override
Widget build(BuildContext context) {
Widget bodyData() => DataTable(
onSelectAll: (b) {},
sortColumnIndex: 0,
sortAscending: true,
columns: <DataColumn>[
DataColumn(label: Text("Name"), tooltip: "To Display name"),
DataColumn(label: Text("Email"), tooltip: "To Display Email"),
DataColumn(label: Text("Update"), tooltip: "Update data"),
],
rows: userList
.map(
(user) => DataRow(
cells: [
DataCell(
Text(user.name),
),
DataCell(
Text(user.email),
),
DataCell(
IconButton(
onPressed: () {
currentIndex = userList.indexOf(user);
_updateTextControllers(user); // new function here
},
icon: Icon(
Icons.edit,
color: Colors.black,
),
),
),
],
),
)
.toList(),
);
return Scaffold(
appBar: AppBar(
title: Text("Data add to List Table using Form"),
),
body: Container(
child: Column(
children: <Widget>[
bodyData(),
Padding(
padding: EdgeInsets.all(10.0),
child: Form(
key: form,
child: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: nameController,
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Name',
hintText: 'Name',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid),
),
),
SizedBox(
height: 10,
),
TextFormField(
controller: emailController,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Email',
hintText: 'Email',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid)),
),
SizedBox(
height: 10,
),
Column(
children: <Widget>[
Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextButton(
child: Text("Add"),
onPressed: () {
form.currentState.save();
addUserToList(
nameController.text,
emailController.text,
);
},
),
TextButton(
child: Text("Update"),
onPressed: () {
form.currentState.save();
updateForm();
},
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ElevatedButton(
child: Text("Save and Exit"),
onPressed: () {
form.currentState.save();
addUserToList(
nameController.text,
emailController.text,
);
Navigator.pop(context, userList);
},
),
],
),
],
),
),
],
),
],
),
),
),
),
],
),
),
);
}
void updateForm() {
setState(() {
User user = User(name: nameController.text, email: emailController.text);
userList[currentIndex] = user;
});
}
void _updateTextControllers(User user) {
setState(() {
nameController.text = user.name;
emailController.text = user.email;
});
}
void addUserToList(name, email) {
setState(() {
userList.add(User(name: name, email: email));
});
}
}
So instead of passing data back and forth between pages, its better to implement a state management solution so that you can access your data from anywhere in the app, without having to manually pass anything.
It can be done with any state management solution, here's how you could do it with GetX.
I took all your variables and methods and put them in a Getx class. Anything in this class will be accessible from anywhere in the app. I got rid of setState because that's no longer how things will be updated.
class FormController extends GetxController {
TextEditingController nameController = TextEditingController();
TextEditingController emailController = TextEditingController();
int currentIndex = 0;
List<User> userList = [
User(name: "a", email: "a"),
User(name: "d", email: "b"),
User(name: "c", email: "c"),
];
void updateForm() {
User user = User(name: nameController.text, email: emailController.text);
userList[currentIndex] = user;
update();
}
void updateTextControllers(User user) {
nameController.text = user.name;
emailController.text = user.email;
update();
}
void addUserToList(name, email) {
userList.add(User(name: name, email: email));
update();
}
void updateLanguageInformation() {
// for (var i = 0; i <= userList.length; i++) { // ** don't need nested for loop here **
for (var user in userList) {
print("Name: " + user.name + " Email: " + user.email);
}
// }
}
}
GetX controller can be initialized anywhere before you try to use it, but lets do it main.
void main() {
Get.put(FormController()); // controller init
runApp(MyApp());
}
Here's your page, we find the controller and now all variables and methods come from that controller.
class Episode5 extends StatefulWidget {
#override
_Episode5State createState() => _Episode5State();
}
class _Episode5State extends State<Episode5> {
final form = GlobalKey<FormState>();
static var _focusNode = new FocusNode();
// finding same instance if initialized controller
final controller = Get.find<FormController>();
#override
Widget build(BuildContext context) {
Widget bodyData() => DataTable(
onSelectAll: (b) {},
sortColumnIndex: 0,
sortAscending: true,
columns: <DataColumn>[
DataColumn(label: Text("Name"), tooltip: "To Display name"),
DataColumn(label: Text("Email"), tooltip: "To Display Email"),
DataColumn(label: Text("Update"), tooltip: "Update data"),
],
rows: controller.userList // accessing list from Getx controller
.map(
(user) => DataRow(
cells: [
DataCell(
Text(user.name),
),
DataCell(
Text(user.email),
),
DataCell(
IconButton(
onPressed: () {
controller.currentIndex =
controller.userList.indexOf(user);
controller.updateTextControllers(user);
},
icon: Icon(
Icons.edit,
color: Colors.black,
),
),
),
],
),
)
.toList(),
);
return Scaffold(
appBar: AppBar(
title: Text("Data add to List Table using Form"),
),
body: Container(
child: Column(
children: <Widget>[
// GetBuilder rebuilds when update() is called
GetBuilder<FormController>(
builder: (controller) => bodyData(),
),
Padding(
padding: EdgeInsets.all(10.0),
child: Form(
key: form,
child: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: controller.nameController,
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: InputDecoration(
labelText: 'Name',
hintText: 'Name',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid),
),
),
SizedBox(
height: 10,
),
TextFormField(
controller: controller.emailController,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: InputDecoration(
labelText: 'Email',
hintText: 'Email',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid)),
),
SizedBox(
height: 10,
),
Column(
children: <Widget>[
Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextButton(
child: Text("Add"),
onPressed: () {
form.currentState.save();
controller.addUserToList(
controller.nameController.text,
controller.emailController.text,
);
},
),
TextButton(
child: Text("Update"),
onPressed: () {
form.currentState.save();
controller.updateForm();
},
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ElevatedButton(
child: Text("Save and Exit"),
onPressed: () {
form.currentState.save();
controller.updateLanguageInformation(); // all this function does is print the list
Navigator.pop(
context); // don't need to pass anything here
},
),
],
),
],
),
),
],
),
],
),
),
),
),
],
),
),
);
}
}
And here's your other page. I just threw in a ListView.builder wrapped in a GetBuilder<FormController> for demo purposes. It can now be stateless.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Testing List View Data From second page to first page"),
),
body: Column(
children: <Widget>[
Expanded(
child: GetBuilder<FormController>(
builder: (controller) => ListView.builder(
itemCount: controller.userList.length,
itemBuilder: (context, index) => Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(controller.userList[index].name),
Text(controller.userList[index].email),
],
),
),
),
),
SizedBox(
height: 10.0,
),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Episode5(),
),
);
},
child: Text("Go to Form"),
),
],
),
);
}
}
As your app expands you can create more controller classes and they're all very easily accessible from anywhere. Its a way easier and cleaner way to do things than manually passing data around everywhere.

Search Bar Layout with DataTable Flutter

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.

Sorting Columns in Flutters Data Table

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