I'm building my first Flutter app and I have some performance issues.
I have a screen with a CustomScrollView, to especially get a custom AppBar, with a Hero image. Next, I have some fields/filters and then I have my GridView.builder because I can have between 25 to 400 elements in this list, and I want to build them only when the user is scrolling.
But after checking, I saw that all of my elements (400) are built immediately, and makes the app very laggy.
Can you please tell me what I've done wrong?
EDIT: You can test it via this Pastebin: Just simply copy-paste it into a new flutter project https://pastebin.com/xhd9CdUp
Basically, this is the structure of my code:
CustomScrollView
SliverAppBar
SliverPadding
SliverChildListDelegate
SearchField
SizedBox
ValueListenableBuilder // hive package
GridView.builder
CardWithMenuWidget(index)
And here is my code:
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
body: SafeArea(
child: LayoutBuilder(builder: (context, constraints) {
return CustomScrollView(
slivers: [
SliverAppBar(
elevation: 0,
backgroundColor: Colors.transparent,
expandedHeight: 300,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
titlePadding: EdgeInsets.symmetric(vertical: 4),
title: ConstrainedBox(
constraints:
BoxConstraints(maxWidth: constraints.maxWidth * 0.5),
child: Hero(
tag: "extension-logo-${extension.uuid}",
child: CachedImage(imageUrl: extension.logoMediumUrl!),
),
),
background: Align(
alignment: Alignment.topCenter,
child: Hero(
tag: "extension-spotlight-${extension.uuid}",
child: CachedImage(
imageUrl: extension.spotlightMediumUrl!,
fit: BoxFit.fitWidth,
height: 240,
width: double.infinity,
alignment: Alignment.topCenter,
),
),
),
),
),
SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
sliver: SliverList(
delegate: SliverChildListDelegate(
[
SearchField(
controller: this._searchController,
labelText: "Search into ${extension.name}",
cancelText: 'Cancel',
),
SizedBox(height: 32),
// Dynamic item list
ValueListenableBuilder(
valueListenable:
ExtensionBox.box.listenable(keys: [_uuid]),
builder: (context, Box<Extension> box, child) {
var cardUuidList = box.get(_uuid)!.cards?.keys;
return _buildCardListGrid(cardUuidList);
},
),
FakeBottomBarPadding(),
],
),
),
),
],
);
}),
),
);
}
Widget _buildCardListGrid(cardUuidList) {
return LoadingContainer(
isLoading: _isSyncing,
child: cardUuidList == null
? Center(
child: Text('No data.'),
)
: GridView.builder(
addAutomaticKeepAlives: true,
shrinkWrap: true,
physics: ScrollPhysics(),
itemCount: cardUuidList.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 16.0,
crossAxisSpacing: 16.0,
childAspectRatio: CardModel.ratio,
),
itemBuilder: (context, cardIndex) {
String cardUuid = cardUuidList.elementAt(cardIndex);
return CardWithMenuWidget(
uuid: cardUuid,
);
},
),
);
}
Thank you!
My solution, based on my pastebin code:
CustomScrollView(
SliverAppBar(),
...
ValueListenableBuilder(
valueListenable: _list,
builder: (context, listBuilder, child) {
return SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 16.0,
crossAxisSpacing: 16.0,
childAspectRatio: 635 / 889,
),
delegate: SliverChildBuilderDelegate(
(c, i) {
return ItemWidget(listBuilder[i]);
},
childCount: _list.value.length,
),
);
},
),
)
Related
I want to create a ui in flutter like this
And I have managed it. As I have used pageview builder for this, it is asking for a page height, now the problem is that I have given the height according to this screen and now in mobiles with shorter screeen I am not able to get same results.
Container(
margin: EdgeInsets.fromLTRB(20.w, 20.h, 10.w, 3.h),
height: 520.h,
child: Column(
children: [
Expanded(
child: PageView.builder(
itemCount: controller.pages.length,
controller: controller.pageController,
itemBuilder: (context, index) {
return GridView.builder(
// physics: NeverScrollableScrollPhysics(),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemCount: controller.pages[index].length,
itemBuilder: ((context, index1) {
return GestureDetector(
onTap: () async {
//some code
},
child: MenuTileWidget(
title: controller
.pages[index][index1].title,
image: controller
.pages[index][index1].image,
),
);
}));
}),
),
SmoothPageIndicator(
controller: controller.pageController,
count: controller.pages.length,
effect: WormEffect(
activeDotColor: color2,
spacing: 14.w,
dotWidth: 16.sp,
dotHeight: 16.sp,
dotColor: Colors.white),
),
],
),
),
And if I comment height, it gives exception.
Try using MediaQuery() like this:
...
Container(
margin: EdgeInsets.fromLTRB(20.w, 20.h, 10.w, 3.h),
height: MediaQuery.of(context).size.height,
child: Column(
...
This is probably going to hide your indicators. So to solve this use Stack() instead of Column() then control the position of the indicator. Here is the full code:
Container(
margin: EdgeInsets.fromLTRB(20.w, 20.h, 10.w, 3.h),
height: 520.h,
child: Stack(
children: [
Expanded(///<- Remove this you won't need it anymore
child: PageView.builder(
itemCount: controller.pages.length,
controller: controller.pageController,
itemBuilder: (context, index) {
return GridView.builder(
// physics: NeverScrollableScrollPhysics(),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemCount: controller.pages[index].length,
itemBuilder: ((context, index1) {
return GestureDetector(
onTap: () async {
//some code
},
child: MenuTileWidget(
title: controller
.pages[index][index1].title,
image: controller
.pages[index][index1].image,
),
);
}));
}),
),
Align( ///<- Or Positioned
alignment: Alignment.bottomCenter,
child: SmoothPageIndicator(
controller:
controller.pageController,
count:
controller.pages.length,
effect: WormEffect(
activeDotColor: color2,
spacing: 14.w,
dotWidth: 16.sp,
dotHeight: 16.sp,
dotColor:
Colors.white),
),
)
],
),
),
I'm new to Flutter and I like the PageView widget, but how do I always have an AppBar or other elements on top?
Now PageView is changing the entire page, along with the AppBar, how can you pin it? To make the pages scroll under the AppBar
Scaffold(
body: Stack(
children: [
Padding(
padding: EdgeInsets.only(top: 50),
child: Container(
width: double.infinity,
height: 100,
color: Colors.blue,
),
),
FutureBuilder(
future: _futureMenu,
builder: (context, snapshot){
if(snapshot.hasData){
return PageView.builder(
itemBuilder: (context, position) {
return PageForPosition();
},
itemCount: snapshot.data.length, // Can be null
);
} else if (snapshot.hasError){
}
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
}
),
],
),
)
Place the AppBar widget in scaffold appBar parameter.
Scaffold(
appBar: AppBar(),//<-- Move appbar here.
body: Stack(
children: [
FutureBuilder(
future: _futureMenu,
builder: (context, snapshot){
if(snapshot.hasData){
return PageView.builder(
itemBuilder: (context, position) {
return PageForPosition();
},
itemCount: snapshot.data.length, // Can be null
);
} else if (snapshot.hasError){
return Center(child:Text('Error'));
}
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
}
),
],
),
)
Check this dart pad.
You could try use sliverAppbar code like this
SliverAppBar(
expandedHeight: 300.0,
pinned: true,
Pinned allows it to stay fixed to the top if you need additional help on this way of creating an app bar let me know happy to help further explain
You can use CustomScrollView like below to gain your UI and effect.
CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('Demo'),
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: Text('Grid Item $index'),
);
},
childCount: 20,
),
),
SliverFixedExtentList(
itemExtent: 50.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text('List Item $index'),
);
},
),
),
],
)
I have this design:
CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('Demo'),
),
),
SliverList(
delegate: SliverChildListDelegate(
[
Text("sds"),
],
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: Text('Grid Item $index'),
);
},
childCount: 10,
),
),
SliverFillRemaining(
child: ReorderableListView(
scrollController: _scrollController,
children: <Widget>[
for (final items in item)
Card(
color: Colors.blueGrey,
key: ValueKey(items),
elevation: 2,
child: ListTile(
title: Text(items),
leading: Icon(
Icons.work,
color: Colors.black,
),
),
),
],
onReorder: reorderData,
),
),
],
),
Everything between the SliverAppBar and the ReorderableListView is just an example of how it should work.
I need to order some elements so for that I use a ReorderableListView but this needs to be inside a CustomScrollView and using its scroll. Right now everything works except for the scroll. The SliverAppBar minimises when scrolling over the grid, but the ReorderableListView has it owns scroll.
Is there any way to cancel the second scroll and having these 2 elements working together?
I also uses this library https://pub.dev/packages/reorderables but if you have a look to the repo it seems abandoned (and I faced different issues)
I can't scroll to see my lower grid view. Why is that? What should I change to make this work? Please help!!!
Thank you in advance.
Scaffold(
body: SingleChildScrollView(
child: Container(
child: Column(
children: <Widget>[
Text('GridView 1'),
GridView.count(
crossAxisCount: 3,
shrinkWrap: true,
children: List.generate(
9,
(index) {
return TouchableImageCard(
imagePath: 'assets/images/view_${index + 1}.jpg',
);
},
),
),
Text('GridView 2'),
GridView.builder(
itemCount: list_item.length,
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return TouchableImageCard(
imagePath: 'assets/images/view_${index + 1}.jpg',
// width: 150,
// height: 150,
);
},
),
],
),
),
),
);
Thank you for helping me in flutter.
It actually does work, just not the way you need it.
The problem is that GridView itself is a scrollable widget too. So when you try to scroll the page, it actually tries to scroll those GridViews and not the SingleChildScrollView.
To disable GridView scrolling ability - you need to add one more parameter.
GridView.count(
physics: NeverScrollableScrollPhysics(),
...
),
Try this:
Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[]
multiple gridview scrollable
Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Text('GridView 1'),
Container(
height: 400,
child: GridView.count(
crossAxisCount: 3,
shrinkWrap: true,
primary: false,
children: List.generate(
20,
(index) {
return TouchableImageCard(
imagePath: 'assets/images/view_${index + 1}.jpg',
);
},
),
),
),
Text('GridView 2'),
Container(
height: 400,
child: GridView.builder(
itemCount: 20,
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return TouchableImageCard(
imagePath: 'assets/images/view_${index + 1}.jpg',
// width: 150,
// height: 150,
);
},
),
),
],
),
),
);
I have an layout with dummy data
body: ListView(
children: <Widget>[
Container(
padding: EdgeInsets.only(right: 15.0),
width: MediaQuery.of(context).size.width - 30.0,
height: MediaQuery.of(context).size.height - 50.0,
child: GridView.count(
crossAxisCount: 2,
primary: false,
crossAxisSpacing: 10.0,
mainAxisSpacing: 15.0,
childAspectRatio: 0.8,
children: <Widget>[
_buildCard('Cookie mint', '\Rp.3.99',
'assets/cookiemint.jpg', false, false, context),
_buildCard('Cookie cream', '\Rp.25.500',
'assets/cookiecream.jpg', true, false, context),
_buildCard('Cookie classic', '\Rp.35.000',
'assets/cookieclassic.jpg', false, true, context),
_buildCard('Cookie choco', '\Rp.51.500',
'assets/cookiechoco.jpg', false, false, context),
],
),
)
],
),
Result
But when I use real data from firestore, my layout is broken. I use ListView.builder to loop this data
body: ListView(
children: <Widget>[
Container(
padding: EdgeInsets.only(right: 15.0),
width: MediaQuery.of(context).size.width - 30.0,
height: MediaQuery.of(context).size.height - 50.0,
child: GridView.count(
crossAxisCount: 2,
primary: false,
crossAxisSpacing: 10.0,
mainAxisSpacing: 15.0,
childAspectRatio: 0.8,
children: <Widget>[
ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) => _buildCardItem(
context,
snapshot.data.documents[index],
false,
'assets/burger.jpg',
false,
),
)
],
),
)
],
),
And result is
I think maybe ListView.builder create a new layout inside my layout? I new with Flutter. Is there a way to repeat the data in another way besides using ListView.builder? Or still have to use it in what way? Need advice. Thanks
Where you have:
children: <Widget>[
ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) => _buildCardItem(
context,
snapshot.data.documents[index],
false,
'assets/burger.jpg',
false,
),
)
]
You have to place a list of widgets. On your attempt, you have put a listView which is a single widget that represents a list of scrollable itens. They are different.
Your itens are based on the snapshot.data.documents list. You can use it with a .map function to create another widget list based on that:
children: snapshot.data.documents.map(
(item) => _buildCardItem(
context,
item,
false,
'assets/burger.jpg',
false,
)
).toList(),
What is the purpose of the outer most ListView you have 3 scrollable widgets nested together this doesn't seem necessary to me and a lot of people have been having issues with ListView firestore and StreamBuilder if its not adding anything I'd try removing it.
snapshot.data.documents.map((DocumentSnapshot document) => document.data['//Youlabels']);
After browsing, I fixed with my sample code
children: snapshot.data.documents.map<Widget>((document) {
return ListTile(
title: Text(document['item_name']),
subtitle: Text(document['item_price']),
);
}).toList(),