How I can handle Nested TabBar with dynamic length in Flutter - flutter

I'm trying to display tabs for each main tabs (Nested Tab Bar).
I have a course page that show the information for this course in SliverAppBar() . Each course has many sections and each section has many exams.
This is my Build method:
#override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double statusBarHeight =
MediaQuery.of(context).padding.top + 56; // 56 is height of Appbar.
return Scaffold(
body: Container(
child: DefaultTabController(
length: _sections.length,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return [
SliverAppBar(
elevation: 0,
title: Text(
widget._course.shortName +
' ' +
widget._course.code.toString(),
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
),
),
actionsIconTheme: IconThemeData(color: widget._course.color),
expandedHeight: height / 2,
floating: true,
pinned: true,
centerTitle: true,
titleSpacing: 5,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
tooltip: 'Back',
splashColor: Colors.transparent,
onPressed: () => Navigator.pop(context),
),
backgroundColor: widget._course.color,
flexibleSpace: Container(
padding: EdgeInsets.only(top: statusBarHeight),
child: Text('Course information will be here'),
),
),
SliverPersistentHeader(
floating: false,
delegate: _SliverAppBarDelegate(
TabBar(
indicatorSize: TabBarIndicatorSize.label,
labelPadding: EdgeInsets.symmetric(horizontal: 10),
indicator: CircleTabIndicator(
color: Colors.white,
radius: 2.5,
),
indicatorColor: Colors.white,
isScrollable: true,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
unselectedLabelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.w300),
labelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
tabs: List<Widget>.generate(
_sections.length,
(int index) {
return customTab(_sections[index].id);
},
),
),
widget._course.color,
),
pinned: false,
),
];
},
body: Center(
child: getTabBarConten(),
),
),
),
),
);
}
I get _SliverAppBarDelegate() class from the internet to handle the TabBar() in the NestedScrollView(), this is the code:
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar, this._color);
TabBar _tabBar;
final Color _color;
#override
double get minExtent => _tabBar.preferredSize.height;
#override
double get maxExtent => _tabBar.preferredSize.height;
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new Container(
color: _color,
alignment: Alignment.center,
child: _tabBar,
);
}
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
Now each section tab content that I created it in Build method it's have many exams tabs. getTabBarConten() method:
Widget getTabBarConten() {
return TabBarView(
children: List<Widget>.generate(
_sections.length,
(int index) {
return CustomTabView(
color: widget._course.color,
initPosition: initPosition,
itemCount: _sections[index].exams.length,
tabBuilder: (context, index) => customTab(
_sections[index].exams[index].type.toString(),
isSection: true,
),
pageBuilder: (context, index) => Center(
child: Text(_sections[index].exams[index].supervisor +
' ' +
_sections[index].instructor)),
onPositionChange: (index) {
setState(() {
initPosition = index;
});
},
);
},
),
);
}
This getTabBarConten() method it's for sections tabs content and it's return CustomTabView that return tabs for each exam.
The problem is:
RangeError (index): Invalid value: Not in range 0..1, inclusive: 2
In this example the course has 2 sections and each section has 3 exams. So the itemCount in CustomTabView is 3 and the length of sections is 2 that is the error come from.
If I set the itemCount to same length of sections it's work fine (even if the itemCount is less than the length of sections):
See the image here
But if the itemCount is greater than the length of sections its not working!
See the error here
Why this error happened , I mean there is no relation between them, in getTabBarConten() method it's return TabBarView() for the section tabs and for each tab it's return CustomTabView() that return tab for each exam.
So, Why this this error and can anyone help me? please :(

Thanks for chunhunghan his answer in this question is helped me. It's another way but it's work..
[Update: Sep]
I will try to write my code for the same example. Maybe it will help someone :)
Here the code:
import 'package:flutter/material.dart';
class CustomTabView extends StatefulWidget {
final int itemCount;
final IndexedWidgetBuilder tabBuilder;
final IndexedWidgetBuilder pageBuilder;
final Widget stub;
final ValueChanged<int> onPositionChange;
final ValueChanged<double> onScroll;
final int initPosition;
final Color color;
final bool isExamTabs;
final TabController controller;
CustomTabView({
#required this.itemCount,
#required this.tabBuilder,
#required this.pageBuilder,
this.stub,
this.onPositionChange,
this.onScroll,
this.initPosition,
this.color,
this.isExamTabs = false,
this.controller,
});
#override
_CustomTabsState createState() => _CustomTabsState();
}
class _CustomTabsState extends State<CustomTabView>
with TickerProviderStateMixin {
TabController controller;
int _currentCount;
int _currentPosition;
#override
void initState() {
if (widget.controller == null) {
_currentPosition = widget.initPosition ?? 0;
controller = TabController(
length: widget.itemCount,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation.addListener(onScroll);
_currentCount = widget.itemCount;
} else {
controller = widget.controller;
}
super.initState();
}
#override
void didUpdateWidget(CustomTabView oldWidget) {
if (_currentCount != widget.itemCount) {
controller.animation.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
if (widget.initPosition != null) {
_currentPosition = widget.initPosition;
}
if (_currentPosition > widget.itemCount - 1) {
_currentPosition = widget.itemCount - 1;
_currentPosition = _currentPosition < 0 ? 0 : _currentPosition;
if (widget.onPositionChange is ValueChanged<int>) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
widget.onPositionChange(_currentPosition);
}
});
}
}
_currentCount = widget.itemCount;
setState(() {
controller = TabController(
length: widget.itemCount,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation.addListener(onScroll);
});
} else if (widget.initPosition != null) {
controller.animateTo(widget.initPosition);
}
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
controller.animation.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (widget.itemCount < 1) return widget.stub ?? Container();
double height = MediaQuery.of(context).size.height;
return Container(
height: height - 100,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
color: widget.color,
alignment: Alignment.center,
child: widget.isExamTabs
? TabBar(
controller: controller,
indicatorSize: TabBarIndicatorSize.label,
indicatorWeight: 3.5,
indicatorColor: Colors.white,
isScrollable: true,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
unselectedLabelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.w300),
labelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
tabs: List.generate(
widget.itemCount,
(index) => widget.tabBuilder(context, index),
),
)
: TabBar(
controller: controller,
indicatorSize: TabBarIndicatorSize.label,
labelPadding: EdgeInsets.symmetric(horizontal: 10),
indicatorColor: Colors.white,
isScrollable: true,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
unselectedLabelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.w300),
labelStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
tabs: List.generate(
widget.itemCount,
(index) => widget.tabBuilder(context, index),
),
),
),
Expanded(
child: TabBarView(
controller: controller,
children: List.generate(
widget.itemCount,
(index) => widget.pageBuilder(context, index),
),
),
),
],
),
);
}
onPositionChange() {
if (!controller.indexIsChanging) {
_currentPosition = controller.index;
if (widget.onPositionChange is ValueChanged<int>) {
widget.onPositionChange(_currentPosition);
}
}
}
onScroll() {
if (widget.onScroll is ValueChanged<double>) {
widget.onScroll(controller.animation.value);
}
}
}
I called it in my course page's body: (Please see the comments in the code)
CustomTabView(
initPosition: 0,
itemCount: _course.sections.length,
tabBuilder: (context, index) =>
secionTab(_course.sections[index].id), // Sections tabs
pageBuilder: (context, index) => getSectionTabBarConten(index), // Content for each section. To show exams for "_course.sections[index]" call inside it "CustomTabView()" again for exams. It's mean all Exams per secion.
onPositionChange: (index) {},
// onScroll: (position) => print("POS : " + '$position'),
color: _course.getColor(),
)

Related

How can I solve this problem that two keyboard cursors in textfield?

https://i.stack.imgur.com/YKClg.png
I have multiple textfields and I have written code that expects that clearing index 0, i.e. the first textfield in the list, will set the focus back to the first textfield, but contrary to expectations, the cursor moves to the second field. text field. , and why do I see two cursors when I click on another textfield in this state?
`
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:todolist/dataForm.dart';
class TodoCase extends StatefulWidget {
final String title;
const TodoCase({
Key? key,
required this.title,
}) : super(key: key);
#override
State<TodoCase> createState() => _TodoCaseState();
}
class _TodoCaseState extends State<TodoCase> {
List<ToDoDataForm> toDoLineList = [];
final FocusScopeNode textListScopeNode = FocusScopeNode();
final ScrollController toDoScrollController = ScrollController();
List<TextEditingController> textEditController = [];
List<FocusNode> keyBoardFocusNode = [];
List<FocusNode> textFocusNode = [];
Future<void> scrollToEnd() async {
await Future.delayed(const Duration(milliseconds: 100));
toDoScrollController.animateTo(
toDoScrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 400),
curve: Curves.ease,
);
}
#override
Widget build(BuildContext context) {
void addList() {
setState(() {
toDoLineList.add(ToDoDataForm(todoData: ""));
scrollToEnd();
});
}
Widget renderTextFormField(ToDoDataForm dataForm, int index) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Checkbox(
value: dataForm.isDone,
onChanged: (value) {
setState(() {
dataForm.isDone = value!;
});
}),
Flexible(
child: KeyboardListener(
focusNode: keyBoardFocusNode[index],
onKeyEvent: (value) {
if (value.logicalKey == LogicalKeyboardKey.backspace &&
dataForm.todoData.isEmpty) {
setState(() {
if (index != 0) {
textFocusNode[index - 1].requestFocus();
toDoLineList.removeAt(index);
textFocusNode.removeAt(index);
keyBoardFocusNode.removeAt(index);
textEditController.removeAt(index);
} else if (index == 0 && toDoLineList.length == 1) {
toDoLineList.removeAt(index);
textFocusNode.removeAt(index);
textEditController.removeAt(index);
keyBoardFocusNode.removeAt(index);
} else if (index == 0 && toDoLineList.length != 1) {
FocusScope.of(context)
.requestFocus(textFocusNode[index + 1]);
toDoLineList.removeAt(index);
textFocusNode.removeAt(index);
textEditController.removeAt(index);
keyBoardFocusNode.removeAt(index);
}
});
print(textFocusNode);
}
},
child: TextField(
decoration: const InputDecoration(
border: InputBorder.none,
),
autofocus: false,
controller: textEditController[index],
focusNode: textFocusNode[index],
onChanged: (value) {
//print('edit : $index');
dataForm.todoData = value;
//print(textFocusNode);
},
),
),
)
],
);
}
return SizedBox(
width: 180,
height: 200,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.title,
style: const TextStyle(
color: Colors.black,
fontSize: 24,
),
),
IconButton(
onPressed: addList,
icon: const Icon(Icons.add),
padding: EdgeInsets.zero,
iconSize: 20,
),
],
),
Container(
height: 1,
color: Colors.black,
),
Expanded(
child: ListView.builder(
itemCount: toDoLineList.length,
controller: toDoScrollController,
itemBuilder: ((context, index) {
if (textEditController.length - 1 < index) {
print(index);
textEditController.add(TextEditingController(
text: toDoLineList[index].todoData));
textFocusNode.add(FocusNode());
textFocusNode[index].unfocus();
keyBoardFocusNode.add(FocusNode());
}
print(index);
print(textFocusNode);
return renderTextFormField(toDoLineList[index], index);
}),
),
),
],
),
);
}
}
`
i try to focusNode List move to glover and textfield wrap FocusScopeNode and many try things but i can't solve this problem...

The dropdown_search library in flutter does not refresh my user list

Hello everyone and thank you for taking the time to try to help me.
I use this library in flutter on mobile only => pub.dev dropdown_search
When I click on the dropdown, I load with an API call a list of users. I want to call them 20 by 20. The first call is done well, I have my first 20 users displayed and when I get to the bottom of my list I have a new API call that goes to display the next 20. This part also works.
My problem is that the list view in the dropdown doesn't refresh and doesn't show me my next 20 users and yet in the console I have my request that leaves with my 20 new users showing. If I close and reopen the dropdown the list is updated.
I contacted the developer who told me first to try to use this => myKey.currentState.reassemble() with my scrollController but it did not work.
He then explained that to solve my problem he could only see the use of a streambuilder. I tried it but it didn't work.
Would you have some hints to give me or something else please?
Here is a bit of what I did in code. In this example I not use StreamBuilder.
final GlobalObjectKey<DropdownSearchState<ListValueOptionList>> myKey = new GlobalObjectKey<DropdownSearchState<ListValueOptionList>>(50);
bool isListValueModify = false;
List<delegationModele.Delegation> listDelegations = [];
int indexDelegationList;
int pageCounter = 0;
ScrollController _scrollController = ScrollController();
List<ListValueOptionList> listValueOptionListModule;
List<ListValueOptionList> listValueOptionListTypeOfDelegation;
List<ListValueOptionList> listValueOptionsUserDelegation;
List userAlreadyUse = [];
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
print("Scroll event");
_loadValueOptionListSpecificPeople();
setState(() {});
myKey.currentState.reassemble();
}
});
}
#override
Widget build(BuildContext context) {
return Padding(
/// Allows resize when kerboard is open if
/// padding == MediaQuery.of(context).viewInsets
padding: padding,
child: Container(
decoration:BoxDecoration(
color: appStyleMode.primaryBackgroundColor,
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0)),
),
height: modalHeight,
child: Padding(
padding: const EdgeInsets.only(left:30, top:30.0, right: 30, bottom: 20),
child: ListView(
shrinkWrap: false,
children: [
getModificationItemListDelegationBuilder(),
],
),
),
),
);
}
SingleChildScrollView getModificationItemListDelegationBuilder(){
final appStyleMode = Provider.of<AppStyleModeNotifier>(context);
return SingleChildScrollView(
child: Column(
children: [
///Add specific people
Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.only(bottom: 10, top: 10),
child: Text(
"${getTranslated(context, "my_delegation_specific_user")} : ",
style: TextStyle(
color: specificPeopleisEmpty ? Colors.red : appStyleMode.blackWhiteColor,
fontSize: 16,
),
textAlign: TextAlign.start,
),
),
Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: delegation.userDelegationRecipients.length ?? 0,
itemBuilder: (context, index){
return specificPeople(listValueOptionsUserDelegation, index);
},
),
),
],
),
);
}
Widget specificPeople(List<ListValueOptionList> listValueOptionList, int index){
final appStyleMode = Provider.of<AppStyleModeNotifier>(context);
delegationModele.UserDelegationRecipients userDelegation = delegation.userDelegationRecipients[index];
String optionUserDescription;
if(listValueOptionList != null){
listValueOptionList.removeWhere((element) => userLoad.id.stringOf == element.idValue);
for(ListValueOptionList valueOption in listValueOptionList){
if(valueOption.idValue == userDelegation.delegationUserId){
optionUserDescription = valueOption.listValueDescription;
}
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.6,
child: DropdownSearch<ListValueOptionList>(
key: myKey,
dropdownButtonProps: DropdownButtonProps(
color: appStyleMode.blackWhiteColor
),
popupProps: PopupProps.menu(
showSelectedItems: true,
menuProps: MenuProps(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
elevation: 5.0,
backgroundColor: appStyleMode.primaryBackgroundColor,
),
textStyle: TextStyle(
color: appStyleMode.blackWhiteColor
),
scrollbarProps: ScrollbarProps(
thumbVisibility: true,
thumbColor: appStyleMode.categorySelectorSelectedBackgroundColor,
interactive: true
),
listViewProps: ListViewProps(
controller: _scrollController,
shrinkWrap: true,
),
interceptCallBacks: true,
itemBuilder: (context, list, isSelected){
return new Container(
child: ListTile(
selected: isSelected,
title: Text(
list?.listValueDescription,
style: TextStyle(
color: appStyleMode.blackWhiteColor
),
),
),
);
},
loadingBuilder: (context, loadingText){
return Align(
alignment: Alignment.topCenter,
child: CircularProgressIndicator(),
);
},
emptyBuilder: (context, text){
return Align(
alignment: Alignment.topCenter,
child: Text(
"${getTranslated(context, "my_delegation_empty_search_user")}",
style: TextStyle(
color: appStyleMode.blackWhiteColor,
),
),
);
},
showSearchBox: true,
searchDelay: Duration(seconds: 1),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
enabled: true,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: appStyleMode.categorySelectorSelectedBackgroundColor,
style: BorderStyle.solid
)
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
style: BorderStyle.solid
)
),
prefixIcon: Icon(
Icons.search_rounded,
size: 20,
),
hintText: "${getTranslated(context, "my_delegation_search_hint_text")}",
hintStyle: TextStyle(
fontSize: 13
),
),
style: TextStyle(
color: appStyleMode.blackWhiteColor,
),
),
),
onChanged: (newValue){
//OnChanges is well used
},
compareFn: (value1, value2) => value1.idValue == value2.idValue,
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none
)
),
//items: listValueOptionsUserDelegation,
asyncItems: (filter) async{
return _loadValueOptionListSpecificPeople();
},
itemAsString: (ListValueOptionList list) => list.listValueDescription,
selectedItem: new ListValueOptionList(idValue: userDelegation.delegationUserId, listValueDescription: optionUserDescription),
dropdownBuilder: (context, selectedItem){
if(optionUserDescription == null || optionUserDescription.isEmpty){
return Container();
}else if(optionUserDescription == ""){
return Text("");
}else{
selectedItem.listValueDescription = optionUserDescription;
}
return textValueDescription(selectedItem.listValueDescription, appStyleMode, TextAlign.start);
},
),
),
],
);
}else{
return Container();
}
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
I've had a similar app. Maybe this code will help:
class PostsList extends StatefulWidget {
#override
State<PostsList> createState() => _PostsListState();
}
class _PostsListState extends State<PostsList> {
final _scrollController = ScrollController();
#override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
#override
Widget build(BuildContext context) {
return BlocBuilder<PostBloc, PostState>(
builder: (context, state) {
switch (state.status) {
case PostStatus.failure:
return const Center(child: Text('failed to fetch posts'));
case PostStatus.success:
if (state.posts.isEmpty) {
return const Center(child: Text('no posts'));
}
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return index >= state.posts.length
? const BottomLoader()
: PostListItem(post: state.posts[index]);
},
itemCount: state.hasReachedMax
? state.posts.length
: state.posts.length + 1,
controller: _scrollController,
);
default:
return const Center(child: CircularProgressIndicator());
}
},
);
}
#override
void dispose() {
_scrollController
..removeListener(_onScroll)
..dispose();
super.dispose();
}
void _onScroll() {
if (_isBottom) context.read<PostBloc>().add(PostFetched());
}
bool get _isBottom {
if (!_scrollController.hasClients) return false;
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.offset;
return currentScroll >= (maxScroll * 0.9);
}
}
I've used bloc for this but it should work as well with future builder and using snapshot. Basically user scrolls down, and if scrolled more than 90% of the list another getter is send to get more items from api. If you want to replicate the whole bloc (which I strongly recommend) take look at this documentation.

TabBar scroll/slide making inaccuracy of displaying

I uses Container 's color to make indicator-like rather than using TabBar's indicator as I've to implement some animation to the Container.
When TabController index is changing, setState is called in the listener. Tries scroll/slide on the TabBar, the TabBar isn't properly changing the index, as listener doesn't listen to animation for TabBar.
I've tried using tabcontroller.animation.addListener method, but there isn't any workaround for me to control the scroll movement.
Attached video below demonstrates tapping and scroll/slide applied on the TabBar.
TabBar-Scroll/Slide
Code:
class TabTest extends StatefulWidget {
#override
_TabTestState createState() => _TabTestState();
}
class _TabTestState extends State<TabTest> with TickerProviderStateMixin {
late TabController _tabController;
late List<AnimationController> _animationControllers;
#override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this)
..addListener(_listener);
_animationControllers = List.generate(
4,
(i) => AnimationController(
vsync: this,
duration: Duration(milliseconds: 750),
reverseDuration: Duration(milliseconds: 350),
));
}
#override
Widget build(BuildContext context) {
List<IconData> _tabIconData = [
Icons.card_giftcard,
Icons.confirmation_num_outlined,
Icons.emoji_events_outlined,
Icons.wine_bar_outlined,
];
List<String> _tabLabel = [
'Tab1',
'Tab2',
'Tab3',
'Tab4',
];
Widget _tab({
required IconData iconData,
required String label,
required bool isSelectedIndex,
// required double widthAnimation,
// required heightAnimation,
}) {
const _tabTextStyle = TextStyle(
fontWeight: FontWeight.w300, fontSize: 12, color: Colors.black);
return AnimatedContainer(
duration: Duration(milliseconds: 300),
padding: EdgeInsets.only(bottom: 2.0),
height: 55,
width: double.infinity, //_animContainerWidth - widthAnimation,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: isSelectedIndex ? Colors.black : Colors.transparent,
width: 2.0,
),
),
),
child: Tab(
iconMargin: EdgeInsets.only(bottom: 5.0),
icon: Icon(iconData, color: Colors.black),
child: Text(label, style: _tabTextStyle),
),
);
}
List<Widget> _animationGenerator() {
return List.generate(
4,
(index) => ClipRRect(
child: AnimatedBuilder(
animation: _animationControllers[index],
builder: (ctx, _) {
final value = _animationControllers[index].value;
final angle = math.sin(value * math.pi * 2) * math.pi * 0.04;
return Transform.rotate(
angle: angle,
child: _tab(
iconData: _tabIconData[index],
label: _tabLabel[index],
isSelectedIndex: _tabController.index == index,
));
}),
),
);
}
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(100),
child: AppBar(
iconTheme: Theme.of(context).iconTheme,
title: Text(
'Tab Bar',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w400,
),
),
centerTitle: true,
bottom: PreferredSize(
preferredSize: Size.fromHeight(20),
child: Container(
child: TabBar(
controller: _tabController,
labelPadding: EdgeInsets.only(top: 5.0, bottom: 2.0),
indicatorColor: Colors.transparent,
tabs: _animationGenerator(),
),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.white,
spreadRadius: 5.0,
offset: Offset(0, 3))
],
),
),
),
),
),
body: TabBarView(
controller: _tabController,
children: List.generate(
4,
(index) => FittedBox(
child: Text('Tab $index'),
)),
),
);
}
void _listener() {
if (_tabController.indexIsChanging) {
setState(() {}); // To refresh color for Container bottom Border
_animationControllers[_tabController.previousIndex].reverse();
} else {
_animationControllers[_tabController.index].forward();
}
}
#override
void dispose() {
super.dispose();
_tabController.removeListener(_listener);
}
}
this is a solution with a CustomPaint widget driven by TabController.animation:
class TabTest extends StatefulWidget {
#override
_TabTestState createState() => _TabTestState();
}
class _TabTestState extends State<TabTest> with TickerProviderStateMixin {
late TabController _tabController;
late List<AnimationController> _animationControllers;
#override
void initState() {
super.initState();
// timeDilation = 10;
_tabController = TabController(length: 4, vsync: this)
..addListener(_listener);
_animationControllers = List.generate(4, (i) => AnimationController(
vsync: this,
duration: Duration(milliseconds: 750),
));
}
#override
Widget build(BuildContext context) {
List<IconData> _tabIconData = [
Icons.card_giftcard,
Icons.confirmation_num_outlined,
Icons.emoji_events_outlined,
Icons.wine_bar_outlined,
];
List<String> _tabLabel = [
'Tab1',
'Tab2',
'Tab3',
'Tab4',
];
List<Color> _tabColor = [
Color(0xffaa0000),
Color(0xff00aa00),
Color(0xff0000aa),
Colors.black,
];
Widget _tab({
required IconData iconData,
required String label,
required Color color,
required int index,
required Animation<double>? animation,
}) {
const _tabTextStyle = TextStyle(fontWeight: FontWeight.w300, fontSize: 12, color: Colors.black);
return CustomPaint(
painter: TabPainter(
animation: animation!,
index: index,
color: color,
),
child: SizedBox(
width: double.infinity,
child: Tab(
iconMargin: EdgeInsets.only(bottom: 5.0),
icon: Icon(iconData, color: Colors.black),
child: Text(label, style: _tabTextStyle),
),
),
);
}
List<Widget> _animationGenerator() {
return List.generate(
4,
(index) => AnimatedBuilder(
animation: _animationControllers[index],
builder: (ctx, _) {
final value = _animationControllers[index].value;
final angle = sin(value * pi * 3) * pi * 0.04;
return Transform.rotate(
angle: angle,
child: _tab(
iconData: _tabIconData[index],
label: _tabLabel[index],
color: _tabColor[index],
index: index,
animation: _tabController.animation,
));
}),
);
}
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text('Tab Bar',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w400,
),
),
centerTitle: true,
bottom: TabBar(
controller: _tabController,
labelPadding: EdgeInsets.only(top: 5.0, bottom: 2.0),
indicatorColor: Colors.transparent,
tabs: _animationGenerator(),
),
),
body: TabBarView(
controller: _tabController,
children: List.generate(4, (index) => FittedBox(
child: Text('Tab $index'),
)),
),
);
}
void _listener() {
if (_tabController.indexIsChanging) {
_animationControllers[_tabController.previousIndex].value = 0;
} else {
_animationControllers[_tabController.index].forward();
}
}
#override
void dispose() {
super.dispose();
_tabController
..removeListener(_listener)
..dispose();
_animationControllers.forEach((ac) => ac.dispose());
}
}
class TabPainter extends CustomPainter {
final Animation<double> animation;
final int index;
final Color color;
final tabPaint = Paint();
TabPainter({
required this.animation,
required this.index,
required this.color,
});
#override
void paint(ui.Canvas canvas, ui.Size size) {
// timeDilation = 10;
if ((animation.value - index).abs() < 1) {
final rect = Offset.zero & size;
canvas.clipRect(rect);
canvas.translate(size.width * (animation.value - index), 0);
final tabRect = Alignment.bottomCenter.inscribe(Size(size.width, 3), rect);
canvas.drawRect(tabRect, tabPaint..color = color);
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

How can i get value from child to parent on button click when button is in parent?

I have been using Stepper view by flutter.And I am having an issue on getting value from child to parent on button click as the button is in parent widget.
Here is my Parent class and my child class.
Parent Class
This is my parent class which has a Stepper view with next and back button.I want to get value from my child class to parent class when next button is clicked.
class DeliveryTimeline extends StatefulWidget {
DeliveryTimeline({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<DeliveryTimeline> {
int _currentStep = 0;
String shippingtype;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
centerTitle: true,
iconTheme: new IconThemeData(color: Colors.black),
elevation: 0,
title: Text(
"Checkout",
style: TextStyle(color: Colors.black),
),
),
body: Stepper(
type: StepperType.horizontal,
steps: _mySteps(),
currentStep: this._currentStep,
onStepTapped: (step) {
setState(() {
this._currentStep = step;
});
},
onStepContinue: () {
setState(() {
if (this._currentStep == 0) {
this._currentStep = this._currentStep + 1;
**//need to get value here on first next click**
} else if (this._currentStep == 1) {
this._currentStep = this._currentStep + 1;
} else {
print('Completed, check fields.');
}
});
},
onStepCancel: () {
setState(() {
if (this._currentStep > 0) {
this._currentStep = this._currentStep - 1;
} else {
this._currentStep = 0;
}
});
},
controlsBuilder: (BuildContext context,
{VoidCallback onStepContinue,
VoidCallback onStepCancel,
Function onShippingNextClick}) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
OutlineButton(
child: new Text("Back"),
onPressed: onStepCancel,
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(4.0))),
MaterialButton(
child: Text("Next"),
color: AppColors.primarycolor,
textColor: Colors.white,
onPressed: onStepContinue,
),
],
);
}));
}
List<Step> _mySteps() {
List<Step> _steps = [
Step(
title: Text('Delivery'),
content: Center(
child: Container(
height: MediaQuery.of(context).size.height / 1.5,
child: Delivery(onShipingTypeClicked: (shippingtype){
shippingtype = shippingtype;
print("myvalue${shippingtype}");
},),
),
),
isActive: _currentStep >= 0,
),
Step(
title: Text('Address'),
content: Address(),
isActive: _currentStep >= 1,
),
Step(
title: Text('Payment'),
content: Payment(),
isActive: _currentStep >= 2,
)
];
return _steps;
}
}
Child Class
This is my child class i have a Listview which act like a radio button.I want the selected item and its value to the parent class when button is clicked..
class Delivery extends StatefulWidget {
final ValueChanged<String> onShipingTypeClicked;
Delivery({this.onShipingTypeClicked});
#override
_DeliveryState createState() => _DeliveryState();
}
class _DeliveryState extends State<Delivery> {
List<RadioModel> sampleData = new List<RadioModel>();
#override
void initState() {
// TODO: implement initState
super.initState();
sampleData.add(new RadioModel(false, 'A', 0xffe6194B, "Standard Delivery",
"Order will be delivered between 3 - 5 business days", 1));
sampleData.add(new RadioModel(
true,
'A',
0xffe6194B,
"Next Day Delivery",
"Place your order before 6pm and your items will be delivered the next day",
2));
sampleData.add(new RadioModel(
false,
'A',
0xffe6194B,
"Nominated Delivery",
"Pick a particular date from the calendar and order will be delivered on selected date",
3));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new ListView.builder(
itemCount: sampleData.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return new InkWell(
onTap: () {
setState(() {
sampleData.forEach((element) => element.isSelected = false);
sampleData[index].isSelected = true;
widget.onShipingTypeClicked(sampleData[index].buttonText);
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextSmallTitleSize(
title: sampleData[index].title,
),
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Flexible(
child: TextSmallDimText(sampleData[index].label),
),
RadioItem(sampleData[index]),
],
),
)
],
));
},
),
);
}
}
class RadioItem extends StatelessWidget {
final RadioModel _item;
RadioItem(this._item);
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.all(15.0),
child: new Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Container(
height: 25.0,
width: 25.0,
alignment: Alignment.center,
child: Container(
height: 15.0,
width: 15.0,
decoration: new BoxDecoration(
color: AppColors.primarycolor,
borderRadius:
const BorderRadius.all(const Radius.circular(15)),
)),
decoration: new BoxDecoration(
color: Colors.transparent,
border: new Border.all(
width: 3.0,
color: _item.isSelected
? AppColors.primarycolor
: Colors.transparent),
borderRadius: const BorderRadius.all(const Radius.circular(25)),
),
),
new Container(margin: new EdgeInsets.only(left: 10.0))
],
),
);
}
}
class RadioModel {
bool isSelected;
final String buttonText;
final int colorCode;
final String title, label;
final int buttonid;
RadioModel(this.isSelected, this.buttonText, this.colorCode, this.title,
this.label, this.buttonid);
}
You can copy paste run full code below
You can use GlobalKey to get deliveryState of Delivery, and use deliveryState to get attribute of Delivery, attribute here is RadioModel selected
Step 1: Use GlobalKey _key = GlobalKey();
class _MyHomePageState extends State<DeliveryTimeline> {
...
GlobalKey _key = GlobalKey();
Step 2: Use deliveryState to get seleted item
onStepContinue: () {
setState(() {
if (this._currentStep == 0) {
this._currentStep = this._currentStep + 1;
final _DeliveryState deliveryState =
_key.currentState;
print("hi ${deliveryState.selected.title} ${deliveryState.selected.label} ");
Step 3: Delivery need key
child: Delivery(
key: _key,
onShipingTypeClicked: (shippingtype) {
...
Delivery({Key key, this.onShipingTypeClicked}) : super(key:key);
Step 4: Set variable selected
RadioModel selected = null;
...
return InkWell(
onTap: () {
setState(() {
...
selected = sampleData[index];
working demo
output of working demo
I/flutter ( 6246): hi Standard Delivery Order will be delivered between 3 - 5 business days
full code
import 'package:flutter/material.dart';
class DeliveryTimeline extends StatefulWidget {
DeliveryTimeline({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<DeliveryTimeline> {
int _currentStep = 0;
String shippingtype;
GlobalKey _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
centerTitle: true,
iconTheme: IconThemeData(color: Colors.black),
elevation: 0,
title: Text(
"Checkout",
style: TextStyle(color: Colors.black),
),
),
body: Stepper(
type: StepperType.horizontal,
steps: _mySteps(),
currentStep: this._currentStep,
onStepTapped: (step) {
setState(() {
this._currentStep = step;
});
},
onStepContinue: () {
setState(() {
if (this._currentStep == 0) {
this._currentStep = this._currentStep + 1;
final _DeliveryState deliveryState =
_key.currentState;
print("hi ${deliveryState.selected.title} ${deliveryState.selected.label} ");
} else if (this._currentStep == 1) {
this._currentStep = this._currentStep + 1;
} else {
print('Completed, check fields.');
}
});
},
onStepCancel: () {
setState(() {
if (this._currentStep > 0) {
this._currentStep = this._currentStep - 1;
} else {
this._currentStep = 0;
}
});
},
controlsBuilder: (BuildContext context,
{VoidCallback onStepContinue,
VoidCallback onStepCancel,
Function onShippingNextClick}) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
OutlineButton(
child: Text("Back"),
onPressed: onStepCancel,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0))),
MaterialButton(
child: Text("Next"),
color: Colors.blue,
textColor: Colors.white,
onPressed: onStepContinue,
),
],
);
}));
}
List<Step> _mySteps() {
List<Step> _steps = [
Step(
title: Text('Delivery'),
content: Center(
child: Container(
height: MediaQuery.of(context).size.height / 1.5,
child: Delivery(
key: _key,
onShipingTypeClicked: (shippingtype) {
shippingtype = shippingtype;
print("myvalue${shippingtype}");
},
),
),
),
isActive: _currentStep >= 0,
),
Step(
title: Text('Address'),
content: Text("Address()"),
isActive: _currentStep >= 1,
),
Step(
title: Text('Payment'),
content: Text("Payment()"),
isActive: _currentStep >= 2,
)
];
return _steps;
}
}
class Delivery extends StatefulWidget {
final ValueChanged<String> onShipingTypeClicked;
Delivery({Key key, this.onShipingTypeClicked}) : super(key:key);
#override
_DeliveryState createState() => _DeliveryState();
}
class _DeliveryState extends State<Delivery> {
List<RadioModel> sampleData = List<RadioModel>();
#override
void initState() {
// TODO: implement initState
super.initState();
sampleData.add(RadioModel(false, 'A', 0xffe6194B, "Standard Delivery",
"Order will be delivered between 3 - 5 business days", 1));
sampleData.add(RadioModel(
true,
'A',
0xffe6194B,
"Next Day Delivery",
"Place your order before 6pm and your items will be delivered the next day",
2));
sampleData.add(RadioModel(
false,
'A',
0xffe6194B,
"Nominated Delivery",
"Pick a particular date from the calendar and order will be delivered on selected date",
3));
}
RadioModel selected = null;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: sampleData.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
setState(() {
sampleData.forEach((element) => element.isSelected = false);
sampleData[index].isSelected = true;
selected = sampleData[index];
widget.onShipingTypeClicked(sampleData[index].buttonText);
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ListTile(
title: Text(sampleData[index].title),
),
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Flexible(
child: Text(sampleData[index].label),
),
RadioItem(sampleData[index]),
],
),
)
],
));
},
),
);
}
}
class RadioItem extends StatelessWidget {
final RadioModel _item;
RadioItem(this._item);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(15.0),
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
height: 25.0,
width: 25.0,
alignment: Alignment.center,
child: Container(
height: 15.0,
width: 15.0,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius:
const BorderRadius.all(const Radius.circular(15)),
)),
decoration: BoxDecoration(
color: Colors.transparent,
border: Border.all(
width: 3.0,
color: _item.isSelected ? Colors.blue : Colors.transparent),
borderRadius: const BorderRadius.all(const Radius.circular(25)),
),
),
Container(margin: EdgeInsets.only(left: 10.0))
],
),
);
}
}
class RadioModel {
bool isSelected;
final String buttonText;
final int colorCode;
final String title, label;
final int buttonid;
RadioModel(this.isSelected, this.buttonText, this.colorCode, this.title,
this.label, this.buttonid);
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: DeliveryTimeline(),
);
}
}

Flutter paginated data table

I'm using PaginatedDataTable widget to show data from api. It works perfectly, so I have set rowsPerPage to 4 and if my data length is for example 7, I get in my first page 4 rows with data and in the second one I get tree others and one empty row. That's the problem, on the second page, I want get only rows that contains data and no empty row. Guess that everyone understands what I mean.
Thank you in advance.
my code =>
class DashboardScreen extends StatefulWidget {
DashboardScreen(
{this.token,
this.f_name,
this.phone,
this.l_name,
this.type_client,
this.email,
this.addresse,
this.num_client});
String token;
final String f_name;
final String l_name;
final String email;
final String addresse;
final String num_client;
final String phone;
final String type_client;
#override
_DashboardScreenState createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen>
with SingleTickerProviderStateMixin {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
AnimationController _animationController;
Animation<double> _animateIcon;
bool isOpened = false;
String token;
String f_name;
String l_name;
String email;
String addresse;
String num_client;
String phone, type_client;
bool showSpinner = false;
List<Transaction> transactions = [];
int rowsPerPAge = 4;
Future<List<Transaction>> _getTransactionsData() async {
setState(() {
showSpinner = true;
});
GetToken getToken = GetToken(token: "${widget.token}");
var freshToken = await getToken.getTokenData();
try {
Map<String, String> newHeader = {'Authorization': 'Bearer $freshToken'};
GetUserData getUserData = GetUserData(
'${APIConstants.API_BASE_URL}/...', newHeader);
var userData = await getUserData.getData();
//print(userData);
var apiError = userData['error'];
var apiCode = userData['code'];
try {
if (apiError == false && apiCode == 200) {
final items = userData['data'].cast<Map<String, dynamic>>();
List<Transaction> listOfTransactions = items.map<Transaction> ((json) {
return Transaction.fromJson(json);
}).toList();
setState(() {
showSpinner = false;
});
//print("succes: $userData");
return listOfTransactions;
}
else if(apiCode == 401){
setState(() {
_scaffoldKey.currentState.showSnackBar(new SnackBar(
backgroundColor: MyColor.hintColor,
elevation: 10,
content: new Text(SnackBarText.TIME_OUT, style: GoogleFonts.roboto(color: Colors.white, fontSize: 20)),
));
showSpinner = false;
return MaterialPageRoute(builder: (context) => LoginScreen());
});
}
else {
//print("erreur: $userData");
setState(() {
_scaffoldKey.currentState.showSnackBar(new SnackBar(
backgroundColor: MyColor.hintColor,
elevation: 10,
content: new Text(SnackBarText.SERVER_ERROR,
style: GoogleFonts.roboto(color: Colors.white, fontSize: 20)),
));
return showSpinner = false;
});
}
} catch (e) {
print(e);
}
} catch (e) {
print(e);
print('1');
}
}
#override
void initState() {
super.initState();
token = widget.token;
f_name = widget.f_name;
l_name = widget.l_name;
email = widget.email;
addresse = widget.addresse;
num_client = widget.num_client;
phone = widget.phone;
type_client = widget.type_client;
_getTransactionsData().then((transactionsFromServer) {
transactions = transactionsFromServer;
//print(transactions);
});
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 500))
..addListener(() {
setState(() {});
});
_animateIcon =
Tween<double>(begin: 1.0, end: 2.0).animate(_animationController);
}
#override
Widget build(BuildContext context) {
//FactureProvider factureProvider = Provider.of<FactureProvider>(context);
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
var orientation = MediaQuery.of(context).orientation;
bool portrait = orientation == Orientation.portrait;
return WillPopScope(
child: Scaffold(
key: _scaffoldKey,
backgroundColor: MyColor.myBackgroundColor,
appBar: AppBar(
automaticallyImplyLeading: false,
centerTitle: false,
backgroundColor: Colors.white,
leading: new IconButton(
padding: EdgeInsets.all(0.0),
icon: new Icon(
Icons.apps,
color: MyColor.menuColor,
),
onPressed: () => _scaffoldKey.currentState.openDrawer()),
title: FittedBox(
fit: BoxFit.fitWidth,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
//SizedBox(width: 30,),
//ImageIcon(AssetImage('images/notification.png'), color: Colors.black,),
Text(
Texts.DASHBOARD,
style: GoogleFonts.roboto(
color: MyColor.hintColor,
fontWeight: FontWeight.bold,
fontSize: 20),
textAlign: TextAlign.left,
),
SizedBox(
width: portrait ? width/4.0 : width/1.5,
),
GestureDetector(
onTap: _goToProfilScreen,
child: ImageIcon(
AssetImage('images/noun_avatar_1.png'),
color: Colors.black,
)),
],
),
),
),
drawer: buildDrawer,
body: Loader(
color: Colors.white.withOpacity(0.3),
loadIng: showSpinner,
child: ListView(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
Row(
children: <Widget>[
Conditioned(
cases:[
Case( (transactions?.length == 0 || transactions?.length == null), builder: () => Padding(
padding: const EdgeInsets.only(left: 10.0, top: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
children: <Widget>[
Text(Texts.HISTO_TRANSAC, style: GoogleFonts.roboto(fontSize: 14, fontWeight: FontWeight.w700), textAlign: TextAlign.start,)
],
),
Padding(
padding: const EdgeInsets.all(20.0),
child: Center(child: Text(Texts.NO_TRANSAC, style: GoogleFonts.roboto(color: MyColor.hintColor, fontSize: 15),),),
),
],
),
)),
],
defaultBuilder: () => Padding(
padding: const EdgeInsets.only(left: 1.0, right: 1.0),
child: FittedBox(
fit: BoxFit.fitWidth,
child: Container(
width: MediaQuery.of(context).size.width/1.005,
child: PaginatedDataTable(
header: Text(Texts.HISTO_TRANSAC, style: GoogleFonts.roboto(fontSize: 14, fontWeight: FontWeight.w700), textAlign: TextAlign.start,),
rowsPerPage: transactions.length <= rowsPerPAge ? transactions.length : rowsPerPAge,
horizontalMargin: 3.7,
columnSpacing: 1.8,
headingRowHeight: 15,
dataRowHeight: 30,
columns: [
DataColumn(label: Text(Texts.dATE, style: GoogleFonts.roboto(fontSize: 8, fontWeight: FontWeight.w900))),
DataColumn(label: Text(Texts.mONTANT_PAYE, style: GoogleFonts.roboto(fontSize: 8, fontWeight: FontWeight.w900))),
DataColumn(label: Text(Texts.SERVICE_TEXT, style: GoogleFonts.roboto(fontSize: 8, fontWeight: FontWeight.w900))),
DataColumn(label: Text(Texts.mODE_PAIEMENT, style: GoogleFonts.roboto(fontSize: 8, fontWeight: FontWeight.w900))),
DataColumn(label: Text(Texts.DETAILS, style: GoogleFonts.roboto(fontSize: 8, fontWeight: FontWeight.w900))),
],
source: DTS(transactions, context, abonnementsById)
),
),
),
),
),
],
),
),
)
],
),
),
bottomNavigationBar: GestureDetector(
onTap: () => showMaterialModalBottomSheet(
context: context,
useRootNavigator: true,
bounce: true,
//secondAnimation: AnimationController.unbounded(vsync: this, duration: Duration(seconds: 30)),
enableDrag: true,
backgroundColor: Colors.transparent,
builder: (context, scrollController) => buildWrap(context),
),
child: Container(
color: MyColor.bottonNavColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text(
Texts.NEW,
style: GoogleFonts.roboto(
color: MyColor.hintColor,
fontSize: 20,
fontWeight: FontWeight.bold),
),
SizedBox(
height: 50,
width: 50,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: FloatingActionButton(
clipBehavior: Clip.hardEdge,
autofocus: true,
mini: true,
backgroundColor: MyColor.hintColor,
onPressed: () => showMaterialModalBottomSheet(
context: context,
useRootNavigator: true,
bounce: true,
//secondAnimation: AnimationController.unbounded(vsync: this, duration: Duration(seconds: 30)),
enableDrag: true,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
buildWrap(context),
),
child: AnimatedIcon(
icon: AnimatedIcons.event_add,
size: 30,
progress: _animateIcon,
),
),
),
)
],
),
),
),
),
onWillPop: _onWillPop,
);
}
}
You can add a checker on PaginatedDataTable's onPageChanged to check if the number of items to be displayed on the table is less than the default number of rows.
PaginatedDataTable(
rowsPerPage: _rowsPerPage,
source: RowSource(),
onPageChanged: (int? n) {
/// value of n is the number of rows displayed so far
setState(() {
if (n != null) {
debugPrint(
'onRowsPerPageChanged $_rowsPerPage ${RowSource()._rowCount - n}');
/// Update rowsPerPage if the remaining count is less than the default rowsPerPage
if (RowSource()._rowCount - n < _rowsPerPage)
_rowsPerPage = RowSource()._rowCount - n;
/// else, restore default rowsPerPage value
else _rowsPerPage = PaginatedDataTable.defaultRowsPerPage;
} else
_rowsPerPage = 0;
});
},
columns: <DataColumn>[],
)
Here's the complete sample.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
/// Set default number of rows to be displayed per page
var _rowsPerPage = PaginatedDataTable.defaultRowsPerPage;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: PaginatedDataTable(
rowsPerPage: _rowsPerPage,
source: RowSource(),
onPageChanged: (int? n) {
/// value of n is the number of rows displayed so far
setState(() {
if (n != null) {
debugPrint(
'onRowsPerPageChanged $_rowsPerPage ${RowSource()._rowCount - n}');
/// Update rowsPerPage if the remaining count is less than the default rowsPerPage
if (RowSource()._rowCount - n < _rowsPerPage)
_rowsPerPage = RowSource()._rowCount - n;
/// else, restore default rowsPerPage value
else _rowsPerPage = PaginatedDataTable.defaultRowsPerPage;
} else
_rowsPerPage = 0;
});
},
columns: [
DataColumn(
label: Text(
'Foo',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
DataColumn(
label: Text(
'Bar',
style: TextStyle(fontStyle: FontStyle.italic),
),
),
],
),
),
);
}
}
class RowSource extends DataTableSource {
final _rowCount = 26;
#override
DataRow? getRow(int index) {
if (index < _rowCount) {
return DataRow(cells: <DataCell>[
DataCell(Text('Foo $index')),
DataCell(Text('Bar $index'))
]);
} else
return null;
}
#override
bool get isRowCountApproximate => true;
#override
int get rowCount => _rowCount;
#override
int get selectedRowCount => 0;
}