Sorting Columns in Flutters Data Table - flutter

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

Related

Render DataTable rows somoorhly on 1 page in 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),)
);
}
},
),
),
)
)
],
),
);
}
}

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

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.

how to implement slight sound when changing items of ListView or ListWheelScrollView

I have a very simple ListWheelScrollView which I show the years as element in Text widget but it is good for UX to have a slight sound for item changing
ListWheelScrollView(
itemExtent: 80,
onSelectedItemChanged: (int index) {
setState(() {
if (releaseEnd > index + 1950) {
releaseStart = index + 1950;
} else {
return null;
}
});
},
diameterRatio: 4.0,
offAxisFraction: -3.0,
physics: BouncingScrollPhysics(),
children: years
.map((element) => RotatedBox(
quarterTurns: 3,
child: Container(
decoration: element == releaseStart
? BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(4.0),
),
color: Colors.blue,
border: Border.all(
color: Colors.blue, width: 2),
shape: BoxShape.rectangle,
)
: null,
padding: EdgeInsets.all(5),
child: Text(
element.toString(),
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.subtitle
.copyWith(
color: element == releaseStart
? Colors.white
: Colors.black54,
letterSpacing: 2),
),
),
))
.toList(),
),
now is it possible to have a sound for changing it's element?
You can use https://github.com/luanpotter/audioplayers to play sound from asset or url
in your case ListWheelScrollView, when the centered item changes, it will play audio.mp3,
so in onSelectedItemChanged add
audioCache.play('audio.mp3')
example code snippet for use with asset
import 'package:audioplayers/audio_cache.dart';
...
AudioCache audioCache = AudioCache();
...
_btn('Play', () => audioCache.play('audio.mp3')),
...
Widget _btn(String txt, VoidCallback onPressed) {
return ButtonTheme(minWidth: 48.0, child: RaisedButton(child: Text(txt), onPressed: onPressed));
}
full code
import 'dart:async';
import 'dart:io';
import 'package:audioplayers/audio_cache.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:path_provider/path_provider.dart';
import 'player_widget.dart';
typedef void OnError(Exception exception);
const kUrl1 = 'https://luan.xyz/files/audio/ambient_c_motion.mp3';
const kUrl2 = 'https://luan.xyz/files/audio/nasa_on_a_mission.mp3';
const kUrl3 = 'http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio1xtra_mf_p';
void main() {
runApp(new MaterialApp(home: new ExampleApp()));
}
class ExampleApp extends StatefulWidget {
#override
_ExampleAppState createState() => new _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
AudioCache audioCache = AudioCache();
AudioPlayer advancedPlayer = AudioPlayer();
String localFilePath;
Future _loadFile() async {
final bytes = await readBytes(kUrl1);
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/audio.mp3');
await file.writeAsBytes(bytes);
if (await file.exists()) {
setState(() {
localFilePath = file.path;
});
}
}
Widget _tab(List<Widget> children) {
return Center(
child: Container(
padding: EdgeInsets.all(16.0),
child: Column(
children: children.map((w) => Container(child: w, padding: EdgeInsets.all(6.0))).toList(),
),
),
);
}
Widget _btn(String txt, VoidCallback onPressed) {
return ButtonTheme(minWidth: 48.0, child: RaisedButton(child: Text(txt), onPressed: onPressed));
}
Widget remoteUrl() {
return SingleChildScrollView(
child: _tab([
Text(
'Sample 1 ($kUrl1)',
style: TextStyle(fontWeight: FontWeight.bold),
),
PlayerWidget(url: kUrl1),
Text(
'Sample 2 ($kUrl2)',
style: TextStyle(fontWeight: FontWeight.bold),
),
PlayerWidget(url: kUrl2),
Text(
'Sample 3 ($kUrl3)',
style: TextStyle(fontWeight: FontWeight.bold),
),
PlayerWidget(url: kUrl3),
Text(
'Sample 4 (Low Latency mode) ($kUrl1)',
style: TextStyle(fontWeight: FontWeight.bold),
),
PlayerWidget(url: kUrl1, mode: PlayerMode.LOW_LATENCY),
]),
);
}
Widget localFile() {
return _tab([
Text('File: $kUrl1'),
_btn('Download File to your Device', () => _loadFile()),
Text('Current local file path: $localFilePath'),
localFilePath == null ? Container() : PlayerWidget(url: localFilePath, isLocal: true),
]);
}
Widget localAsset() {
return _tab([
Text('Play Local Asset \'audio.mp3\':'),
_btn('Play', () => audioCache.play('audio.mp3')),
Text('Loop Local Asset \'audio.mp3\':'),
_btn('Loop', () => audioCache.loop('audio.mp3')),
Text('Play Local Asset \'audio2.mp3\':'),
_btn('Play', () => audioCache.play('audio2.mp3')),
Text('Play Local Asset In Low Latency \'audio.mp3\':'),
_btn('Play', () => audioCache.play('audio.mp3', mode: PlayerMode.LOW_LATENCY)),
Text('Play Local Asset In Low Latency \'audio2.mp3\':'),
_btn('Play', () => audioCache.play('audio2.mp3', mode: PlayerMode.LOW_LATENCY)),
getLocalFileDuration(),
]);
}
Future<int> _getDuration() async {
File audiofile = await audioCache.load('audio2.mp3');
await advancedPlayer.setUrl(
audiofile.path,
isLocal: true,
);
int duration = await Future.delayed(Duration(seconds: 2), () => advancedPlayer.getDuration());
return duration;
}
getLocalFileDuration() {
return FutureBuilder<int>(
future: _getDuration(),
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('No Connection...');
case ConnectionState.active:
case ConnectionState.waiting:
return Text('Awaiting result...');
case ConnectionState.done:
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
return Text('audio2.mp3 duration is: ${Duration(milliseconds: snapshot.data)}');
}
return null; // unreachable
},
);
}
Widget notification() {
return _tab([
Text('Play notification sound: \'messenger.mp3\':'),
_btn('Play', () => audioCache.play('messenger.mp3', isNotification: true)),
]);
}
Widget advanced() {
return _tab([
Column(children: [
Text('Source Url'),
Row(children: [
_btn('Audio 1', () => advancedPlayer.setUrl(kUrl1)),
_btn('Audio 2', () => advancedPlayer.setUrl(kUrl2)),
_btn('Stream', () => advancedPlayer.setUrl(kUrl3)),
], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
]),
Column(children: [
Text('Release Mode'),
Row(children: [
_btn('STOP', () => advancedPlayer.setReleaseMode(ReleaseMode.STOP)),
_btn('LOOP', () => advancedPlayer.setReleaseMode(ReleaseMode.LOOP)),
_btn('RELEASE', () => advancedPlayer.setReleaseMode(ReleaseMode.RELEASE)),
], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
]),
new Column(children: [
Text('Volume'),
Row(children: [
_btn('0.0', () => advancedPlayer.setVolume(0.0)),
_btn('0.5', () => advancedPlayer.setVolume(0.5)),
_btn('1.0', () => advancedPlayer.setVolume(1.0)),
_btn('2.0', () => advancedPlayer.setVolume(2.0)),
], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
]),
new Column(children: [
Text('Control'),
Row(children: [
_btn('resume', () => advancedPlayer.resume()),
_btn('pause', () => advancedPlayer.pause()),
_btn('stop', () => advancedPlayer.stop()),
_btn('release', () => advancedPlayer.release()),
], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
]),
new Column(children: [
Text('Seek in milliseconds'),
Row(children: [
_btn('100ms', () => advancedPlayer.seek(Duration(milliseconds: 100))),
_btn('500ms', () => advancedPlayer.seek(Duration(milliseconds: 500))),
_btn('1s', () => advancedPlayer.seek(Duration(seconds: 1))),
_btn('1.5s', () => advancedPlayer.seek(Duration(milliseconds: 1500))),
], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
]),
]);
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 5,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(text: 'Remote Url'),
Tab(text: 'Local File'),
Tab(text: 'Local Asset'),
Tab(text: 'Notification'),
Tab(text: 'Advanced'),
],
),
title: Text('audioplayers Example'),
),
body: TabBarView(
children: [remoteUrl(), localFile(), localAsset(), notification(), advanced()],
),
),
);
}
}