Flutter sticky header list add items on scroll - flutter

Hello fellow developers,
For my first Flutter project I need to use a list with sticky headers and infinite scroll. I found a very nice library for this purpose.
https://pub.dev/packages/flutter_sticky_header
Final goal is to fetch new items into the list from my database by scrolling further down.
For testing purposes I added a button to add a random item to my list. However the UI is not updated when the function is called. I am very new to Flutter. Below my code. How can I update the UI every time an item is added to the list without recreating the widget.
class AppScaffold2 extends StatefulWidget {
#override
_AppScaffold2State createState() => _AppScaffold2State();
}
class _AppScaffold2State extends State<AppScaffold2> {
final CustomScrollView x = CustomScrollView(
slivers: new List<Widget>(),
reverse: false,
);
int counter = 0;
add(Widget w){
x.slivers.add(w);
}
#override
Widget build(BuildContext context) {
return DefaultStickyHeaderController(
child: Scaffold(
body: Container(child: Column(children: <Widget>[
Expanded(child: x),
MaterialButton(
onPressed: () => fetch(),
child: Text('add to list')
)
],),)
),
);
}
fetch() {
x.slivers.add(_StickyHeaderList(index: counter));
counter++;
}
}
class _StickyHeaderList extends StatelessWidget {
const _StickyHeaderList({
Key key,
this.index,
}) : super(key: key);
final int index;
#override
Widget build(BuildContext context) {
return SliverStickyHeader(
header: Header(index: index),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) => ListTile(
leading: CircleAvatar(
child: Text('$index'),
),
title: Image.network(
"https://i.ytimg.com/vi/ixkoVwKQaJg/hqdefault.jpg?sqp=-oaymwEZCNACELwBSFXyq4qpAwsIARUAAIhCGAFwAQ==&rs=AOn4CLDrYjizQef0rnqvBc0mZyU3k13yrg",
),
),
childCount: 6,
),
),
);
}
}

Try using setState() in fetch Method.
like this.
fetch() {
x.slivers.add(_StickyHeaderList(index: counter));
setState(() {
_counter++;
});
}```

Update state using setState.
fetch() {
x.slivers.add(_StickyHeaderList(index: counter));
setState(() {
_counter++;
});
}

Related

Switching from ReorderableListView to ListView breaks Interaction of rows

I have a view which switches between a ListView and a ReorderableListView.
Widget _buildList(
BuildContext context,
List<ExerciseTemplate> exerciseTemplates,
EditWorkoutModel dao,
) {
if (_isInEditingMode) {
return ReorderableListView(
key: ObjectKey('reordeableListView'),
onReorder: ((oldIndex, newIndex) {
dao.reorderIndexes(
oldIndex,
(oldIndex < newIndex) ? newIndex - 1 : newIndex,
);
}),
padding: const EdgeInsets.only(bottom: 120),
children: [
for (var exerciseTemplate in exerciseTemplates)
Provider(
key: ObjectKey('${exerciseTemplate.id}_compactExerciseTemplateRow_provider'),
create: (context) => EditExerciseModel(exerciseTemplate),
child: CompactExerciseTemplateRow(
key: ObjectKey('${exerciseTemplate.id}_compactExerciseTemplateRow'),
),
),
],
);
} else {
return ListView.builder(
key: ObjectKey('listView'),
itemCount: exerciseTemplates.length,
padding: const EdgeInsets.only(bottom: 120),
itemBuilder: (BuildContext context, int index) {
final exerciseTemplate = exerciseTemplates[index];
return Provider(
// Key is needed here to properly handle deleted rows in the ui.
// Without the key, deleted rows are being shown.
key: ObjectKey(
'${exerciseTemplate.id}_exerciseTemplateRow_provider'),
create: (context) => EditExerciseModel(exerciseTemplate),
child: ExerciseTemplateRow(
key: ObjectKey('${exerciseTemplate.id}_exerciseTemplateRow'),
onDelete: () async {
await dao.deleteExercise(exerciseTemplate);
return true;
},
),
);
},
);
}
}
Both lists show the same data, but tapping a button, switches to a ReorderableListView which shows the data with different widgets. Tapping the button again switches back to the ListView.
However, switching forth and back results that I am not able to interact with elements within the row of the ListView. This issue appeared after I added a globalKey for each element in the ListView. I need this key, to properly handle deleting rows, so I can not just remove the key again.
How can I make it work, that I can interact with widgets within the row after I switched to the ReorderableListView and back to the ListView?
Copied from Provider document:
https://pub.dev/packages/provider
DON'T create your object from variables that can change over time.
In such a situation, your object would never update when the value changes.
int count;
Provider(
create: (_) => MyModel(count),
child: ...
)
If you want to pass variables that can change over time to your object, consider using ProxyProvider:
int count;
ProxyProvider0(
update: (_, __) => MyModel(count),
child: ...
)
It's ok to use Global key and switch between ListView and ReorderableListView, see example below:
https://dartpad.dev/?id=fd39a89b67448d86e682dd2c5ec77453
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool reOrder = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(reOrder ? "ReoderableListView" : "ListView"),
),
floatingActionButton: FloatingActionButton(onPressed: () {
setState(() {
reOrder = !reOrder;
});
}),
body: MyListView(reOrder));
}
}
final data = List.generate(10, (index) => {"title": 'item $index', "value": false});
class MyListView extends StatefulWidget {
final bool reOrder;
const MyListView(this.reOrder, {super.key});
#override
State<MyListView> createState() => _MyListViewState();
}
class _MyListViewState extends State<MyListView> {
#override
Widget build(BuildContext context) {
if (widget.reOrder) {
return ReorderableListView(
key: const ObjectKey('reordeableListView'),
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final item = data.removeAt(oldIndex);
data.insert(newIndex, item);
});
},
children: [
for (var item in data)
ListTile(
key: ObjectKey('${item["title"]}_compactExerciseTemplateRow_provider'),
title: Text(item["title"] as String),
trailing: Text((item["value"] as bool).toString()),
),
],
);
} else {
return ListView.builder(
key: const ObjectKey('listView'),
itemCount: data.length,
padding: const EdgeInsets.only(bottom: 120),
itemBuilder: (BuildContext context, int index) {
return CheckboxListTile(
key: ObjectKey('${data[index]["title"]}_exerciseTemplateRow_provider'),
title: Text(data[index]["title"] as String),
value: (data[index]["value"] as bool),
onChanged: (bool? value) {
setState(() => data[index]["value"] = value!);
},
);
},
);
}
}
}
So the issue was that I was using ObjectKey instead of ValueKey.
The difference between those two is that ObjectKey checks if the identity (the instance) is the same. ValueKey checks the underlying value with the == operator.
My guess here is that by using ObjectKey in my case, flutter is not able to properly replace the old widget with the new one, since the new widget always have a different key. By using ValueKey flutter can distinguish between old and new widgets. Widgets will be in my case replaced after I switch between the lists, because the row widget won't be visible and therefor disposed.
Because the widgets were not properly replaced, somehow the old widgets are still being rendered, but all gesture listeners were already disposed. Therefor no interaction was possible anymore.
These are just my assumption, let me know if I am completely wrong here.

Pin widget for some seconds when widget comes into viewport in SliverStreamGrid while scrolling

I have CustomScrollView with a SliverGrid with different widget types inside. I have a widget that appears after the stream content ends and other content will load. When this widget appears, I want to shortly pin it on the screen and disable the scrolling for 3 seconds.
To simplify it, I skipped the sliver delegates and summarizes my widget tree like this:
CustomScrollView{
controller: scrollController,
slivers: [
SliverAppBar(),
SliverStreamGrid(
children: [
ProductTile(),
ProductTile(),
ProductTile(),
// End of Available Products
EndOfProductsInfoWidget(), // should be pinned on screen for 3 seconds
SomeOtherProductTile(),
SomeOtherProductTile(),
SomeOtherProductTile(),
]
)
]
}
I am using visibility_detector to detect visibility of the widget and SliverPinnedHeader from sliver_tools package.
The issue lies when our widget is visible I am using a short delay and then disabling scroll event for 3 seconds, you can use global key for this and have more precious output.
CustomScrollView's physics:NeverScrollableScrollPhysics() used to disable scroll event.
class SliverPinTHEx extends StatefulWidget {
const SliverPinTHEx({Key? key}) : super(key: key);
#override
State<SliverPinTHEx> createState() => _SliverPinTHExState();
}
class _SliverPinTHExState extends State<SliverPinTHEx> {
bool? _showPinMessage;
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
physics: _showPinMessage == true
? const NeverScrollableScrollPhysics()
: null,
slivers: [
list(),
if (_showPinMessage != false)
SliverPinnedHeader(
child: VisibilityDetector(
onVisibilityChanged: (info) async {
if (info.visibleFraction == 1.0) {
debugPrint("disable scroll");
await Future.delayed(const Duration(seconds: 1));
setState(() {
_showPinMessage = true;
});
Future.delayed(const Duration(seconds: 3)).then((value) {
setState(() {
_showPinMessage = false;
});
});
}
},
key: const Key('my-widget-key'),
child: Container(
height: 70,
color: Colors.amber,
child: const Text("pinned widget"),
),
)),
list()
],
),
);
}
SliverList list() {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return SizedBox(
height: 50,
child: Text(
index.toString(),
),
);
},
childCount: 55,
),
);
}
}

Flutter : PageController.page cannot be accessed before a PageView is built with it

How to solve the exception -
Unhandled Exception: 'package:flutter/src/widgets/page_view.dart': Failed assertion: line 179 pos 7: 'positions.isNotEmpty': PageController.page cannot be accessed before a PageView is built with it.
Note:- I used it in two screens and when I switch between screen it shows the above exception.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _animateSlider());
}
void _animateSlider() {
Future.delayed(Duration(seconds: 2)).then(
(_) {
int nextPage = _controller.page.round() + 1;
if (nextPage == widget.slide.length) {
nextPage = 0;
}
_controller
.animateToPage(nextPage,
duration: Duration(milliseconds: 300), curve: Curves.linear)
.then(
(_) => _animateSlider(),
);
},
);
}
I think you can just use a Listener like this:
int _currentPage;
#override
void initState() {
super.initState();
_currentPage = 0;
_controller.addListener(() {
setState(() {
_currentPage = _controller.page.toInt();
});
});
}
I don't have enough information to see exactly where your problem is, but I just encountered a similar issue where I wanted to group a PageView and labels in the same widget and I wanted to mark active the current slide and the label so I was needing to access controler.page in order to do that. Here is my fix :
Fix for accessing page index before PageView widget is built using FutureBuilder widget
class Carousel extends StatelessWidget {
final PageController controller;
Carousel({this.controller});
/// Used to trigger an event when the widget has been built
Future<bool> initializeController() {
Completer<bool> completer = new Completer<bool>();
/// Callback called after widget has been fully built
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
completer.complete(true);
});
return completer.future;
} // /initializeController()
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
// **** FIX **** //
FutureBuilder(
future: initializeController(),
builder: (BuildContext context, AsyncSnapshot<void> snap) {
if (!snap.hasData) {
// Just return a placeholder widget, here it's nothing but you have to return something to avoid errors
return SizedBox();
}
// Then, if the PageView is built, we return the labels buttons
return Column(
children: <Widget>[
CustomLabelButton(
child: Text('Label 1'),
isActive: controller.page.round() == 0,
onPressed: () {},
),
CustomLabelButton(
child: Text('Label 2'),
isActive: controller.page.round() == 1,
onPressed: () {},
),
CustomLabelButton(
child: Text('Label 3'),
isActive: controller.page.round() == 2,
onPressed: () {},
),
],
);
},
),
// **** /FIX **** //
PageView(
physics: BouncingScrollPhysics(),
controller: controller,
children: <Widget>[
CustomPage(),
CustomPage(),
CustomPage(),
],
),
],
);
}
}
Fix if you need the index directly in the PageView children
You can use a stateful widget instead :
class Carousel extends StatefulWidget {
Carousel();
#override
_HomeHorizontalCarouselState createState() => _CarouselState();
}
class _CarouselState extends State<Carousel> {
final PageController controller = PageController();
int currentIndex = 0;
#override
void initState() {
super.initState();
/// Attach a listener which will update the state and refresh the page index
controller.addListener(() {
if (controller.page.round() != currentIndex) {
setState(() {
currentIndex = controller.page.round();
});
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Column(
children: <Widget>[
CustomLabelButton(
child: Text('Label 1'),
isActive: currentIndex == 0,
onPressed: () {},
),
CustomLabelButton(
child: Text('Label 2'),
isActive: currentIndex == 1,
onPressed: () {},
),
CustomLabelButton(
child: Text('Label 3'),
isActive: currentIndex == 2,
onPressed: () {},
),
]
),
PageView(
physics: BouncingScrollPhysics(),
controller: controller,
children: <Widget>[
CustomPage(isActive: currentIndex == 0),
CustomPage(isActive: currentIndex == 1),
CustomPage(isActive: currentIndex == 2),
],
),
],
);
}
}
This means that you are trying to access PageController.page (It could be you or by a third party package like Page Indicator), however, at that time, Flutter hasn't yet rendered the PageView widget referencing the controller.
Best Solution: Use FutureBuilder with Future.value
Here we just wrap the code using the page property on the pageController into a future builder, such that it is rendered little after the PageView has been rendered.
We use Future.value(true) which will cause the Future to complete immediately but still wait enough for the next frame to complete successfully, so PageView will be already built before we reference it.
class Carousel extends StatelessWidget {
final PageController controller;
Carousel({this.controller});
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
FutureBuilder(
future: Future.value(true),
builder: (BuildContext context, AsyncSnapshot<void> snap) {
//If we do not have data as we wait for the future to complete,
//show any widget, eg. empty Container
if (!snap.hasData) {
return Container();
}
//Otherwise the future completed, so we can now safely use the controller.page
return Text(controller.controller.page.round().toString);
},
),
//This PageView will be built immediately before the widget above it, thanks to
// the FutureBuilder used above, so whenever the widget above is rendered, it will
//already use a controller with a built `PageView`
PageView(
physics: BouncingScrollPhysics(),
controller: controller,
children: <Widget>[
AnyWidgetOne(),
AnyWidgetTwo()
],
),
],
);
}
}
Alternatively
Alternatively, you could still use a FutureBuilder with a future that completes in addPostFrameCallback in initState lifehook as it also will complete the future after the current frame is rendered, which will have the same effect as the above solution. But I would highly recommend the first solution as it is straight-forward
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
//Future will be completed here
// e.g completer.complete(true);
});
use this widget and modify it as you want:
class IndicatorsPageView extends StatefulWidget {
const IndicatorsPageView({
Key? key,
required this.controller,
}) : super(key: key);
final PageController controller;
#override
State<IndicatorsPageView> createState() => _IndicatorsPageViewState();
}
class _IndicatorsPageViewState extends State<IndicatorsPageView> {
int _currentPage = 0;
#override
void initState() {
widget.controller.addListener(() {
setState(() {
_currentPage = widget.controller.page?.toInt() ?? 0;
});
});
super.initState();
}
#override
void dispose() {
widget.controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
3,
(index) => IndicatorPageview(isActive: _currentPage == index, index: index),
),
);
}
}
class IndicatorPageview extends StatelessWidget {
const IndicatorPageview({
Key? key,
required this.isActive,
required this.index,
}) : super(key: key);
final bool isActive;
final int index;
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(right: 8),
width: 16,
height: 16,
decoration: BoxDecoration(color: isActive ?Colors.red : Colors.grey, shape: BoxShape.circle),
);
}
}

Flutter, How to update a text in an item in listview after updating the content from it's detail view?

I am following this link,
https://medium.com/…/developing-for-multiple-screen-sizes-a…
to create a master detail ipad application.
I have a scenario, there is a text field and button in detail page. When i change the text field value and press the button, the listview item (in left side) at that specific index also should be updated. can somebody suggest a work around?
You can return the edited object using Navigator.pop(context,object) to the Navigator.push() caller. I wrote an example app for you.
the data class:
class Item {
final String name;
Item(this.name);
}
the home page, where I display the item:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Item item = Item('ali2236');
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: Center(
child: Column(
children: <Widget>[
Text(item.name),
FlatButton(
child: Text('edit'),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return ItemEditingPage(
item: item,
callbackFunction: (editedItem){
setState(() {
item = editedItem;
});
},
);
}));
},
),
],
),
),
),
);
}
}
and the editing page:
class ItemEditingPage extends StatefulWidget {
final Item item;
final void Function(Item item) callbackFunction;
const ItemEditingPage({Key key, this.item, this.callbackFunction}) : super(key: key);
#override
_ItemEditingPageState createState() => _ItemEditingPageState();
}
class _ItemEditingPageState extends State<ItemEditingPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: FlatButton(
child: Text('change name to aligator'),
onPressed: () {
///
/// if the name is [final], you create a new Item and pass it back
///
Item item = Item('aligator');
widget.callbackFunction(item);
///
/// if the name is not final you can just change it on the current object
///
//widget.item.name = 'aligator';
//widget.callbackFunction(widget.item);
},
),
),
),
);
}
}
edit: used a callback function instead of Navigator.pop() to notify the showcase page.

I want to check if any Widget exist in a List?

I want to load pages from a List and when the user taps on an item from the drawer he can go to that page (if it's already opened) otherwise the Widget will load in the selected page.
But I can't find if that widget is already exists in the List if(myList.contains(Widget1())) => print('it exist'); One guy told me to override hashCode and operator==
class Widget6 extends StatelessWidget {
final String title = 'Widget6';
final Icon icon = Icon(Icons.assessment);
#override
Widget build(BuildContext context) {
return Center(
child: icon,
);
}
#override
bool operator ==(dynamic other) {
final Widget6 typedOther = other;
return title == typedOther.title && icon == typedOther.icon;
}
#override
int get hashCode => hashValues(title, icon);
}
if I do that I can't use any child widget to those widgets. Getting exception like: type 'Center' is not a subtype of type 'Widget6'. I copied this from flutter gallery I didn't find good documentation/guide. Sorry, I am a beginner.
Complete code below
class _MyHomePageState extends State<MyHomePage> {
List pageList = [
Widget1(),
Widget2(),
Widget3(),
Widget4(),
];
PageController _pageController;
int _selectedIndex = 0;
#override
void initState() {
_pageController = PageController(
initialPage: _selectedIndex,
);
super.initState();
}
void navigatePage(Widget widget) {
// problem is here
if (pageList.contains(widget)) {
_pageController.animateToPage(pageList.indexOf(widget, 0),
duration: Duration(milliseconds: 300), curve: Curves.ease);
}
else {
setState(() {
pageList.removeAt(_pageController.page.toInt());
pageList.insert(_pageController.page.toInt(), widget);
});
_pageController.animateToPage(_pageController.page.toInt(),
duration: Duration(milliseconds: 300), curve: Curves.ease);
}
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text('Widget1'),
onTap: () => navigatePage(
Widget1(),
),
),
ListTile(
title: Text('Widget2'),
onTap: () => navigatePage(
Widget2(),
),
),
ListTile(
title: Text('Widget3'),
onTap: () => navigatePage(
Widget3(),
),
),
ListTile(
title: Text('Widget4'),
onTap: () => navigatePage(
Widget4(),
),
),
ListTile(
title: Text('Widget5'),
onTap: () => navigatePage(
Widget5(),
),
),
ListTile(
title: Text('Widget6'),
onTap: () => navigatePage(
Widget6(),
),
),
],
),
),
appBar: AppBar(
title: Text(widget.title),
),
body: PageView.builder(
onPageChanged: (newPage) {
setState(() {
this._selectedIndex = newPage;
});
},
controller: _pageController,
itemBuilder: (context, index) {
return Container(
child: pageList[index],
);
},
itemCount: pageList.length,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) => setState(() {
_selectedIndex = index;
_pageController.animateToPage(index,
duration: Duration(milliseconds: 300), curve: Curves.ease);
}),
items: pageList.map((page) {
return BottomNavigationBarItem(
backgroundColor: Colors.deepOrangeAccent,
icon: page.icon,
title: Text(page.title));
}).toList(),
),
);
}
}
Here List of dummy Widgets
class Widget1 extends StatelessWidget {
final String title = 'Widget1';
final Icon icon = Icon(Icons.school);
#override
Widget build(BuildContext context) {
return Center(
child: icon,
);
}
}
class Widget2 extends StatelessWidget {
// only title and icon are changed
}
class Widget3 extends StatelessWidget {
// only title and icon are changed
}
class Widget4 extends StatelessWidget {
// only title and icon are changed
}
class Widget5 extends StatelessWidget {
// only title and icon are changed
}
class Widget6 extends StatelessWidget {
// only title and icon are changed
}
Okay, I found the solution. And it has to do with operator== overriding
I missed this line if (runtimeType != other.runtimeType) return false;
The whole code stays the same.
#override
// ignore: hash_and_equals
bool operator ==(dynamic other) {
if (runtimeType != other.runtimeType) return false;
final Widget6 typedOther = other;
return title == typedOther.title;
}
#Ahmed Sorry for the late reply, I decided to put it in an answer rather than a comment.
One solution is yours, overriding == but I was thinking of using Key and then instead of using contains method, using something like:
if(myList.indexWhere((Widget widget)=> widget.key==_key) != -1)...
Suggestion
You can store icon and title as a map or a module instead of making 6 different Widget.
You can create another file, saying module.dart like this:
class Module {
final String title;
final Icon icon;
Module(this.title, this.icon);
#override
int get hashCode => hashValues(title.hashCode, icon.hashCode);
#override
bool operator ==(other) {
if (!identical(this, other)) {
return false;
}
return other is Module &&
this.title.compareTo(other.title) == 0 &&
this.icon == other.icon;
}
}
Then create another file that builds the page, saying mywidget.dart, like this:
class MyWidget extends StatelessWidget {
final Module module;
MyWidget({Key key,#required this.module}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: module.icon,
);
}
}
Then on each ListTile's onTap, Navigate like this:
...
ListTile(
title: Text('Widget1'),
onTap: () => navigatePage(
MyWidget(module: Module('Widget1', Icon(Icons.school)),)
),
),
...
So instead of storing Widgets, you store a Type(Here Module) that you declared.
You can also use the list's map to build each ListTile of the ListView for each Module, instead of doing it one by one. (if each item on the drawer are similar), Something like this:
List<Module> myTabs = [
Module('Widget1', Icon(Icons.school)),
Module('Widget2', Icon(Icons.home)),
];
...
Drawer(
child: ListView(
children:myTabs.map((Module module)=> ListTile(
title:Text( module.title),
onTap: navigatePage(MyWidget(module: module,)),
)).toList(),
) ,
);
...