Infinite Scrolling in GridView.builder - flutter

I am Building a flutter e-commerce application. And in the Categories section. There are so much data so I want to implement pagination through infinite scrolling.
This is what I am trying to do, CatHome object is the category either men or women etc.
the code is resulting in pagination, but everytime it is building another page, not infinite scroll. so i cannot access previosly loaded data
class CategoryDetail extends StatefulWidget {
CatHome catHome;
CategoryDetail(this.catHome);
#override
_CategoryDetailstate createState() => _CategoryDetailstate(catHome);
}
class _CategoryDetailstate extends State<CategoryDetail> {
List<FutureOr> _items = [];
CatHome catHome;
_CategoryDetailstate(this.catHome);
var _currentPage = 1;
ScrollController scroll = ScrollController();
var _hasMore = true;
#override
void initState() {
super.initState();
scroll.addListener(() {
if (scroll.position.pixels == scroll.position.maxScrollExtent) {
_fetchMore();
}
});
}
#override
void dispose() {
scroll.dispose();
super.dispose();
}
void _fetchMore() async {
if (_hasMore) {
setState(() {
_currentPage++;
});
var newItems =
await new catitems_api().fetchdeails(catHome, _currentPage);
if (newItems.isEmpty) {
setState(() {
_hasMore = false;
});
} else {
setState(() {
this._items.addAll(newItems);
});
}
}
}
/// Component widget in flashSale layout
Widget build(BuildContext context) {
var data = EasyLocalizationProvider.of(context).data;
return EasyLocalizationProvider(
data: data,
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
AppLocalizations.of(context).tr('Category'),
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 17.0,
color: Colors.black54,
fontFamily: "Gotik"),
),
centerTitle: true,
iconTheme: IconThemeData(color: Color(0xFF6991C7)),
elevation: 0.0,
),
body: SingleChildScrollView(
child: Container(
child: Column(
children: <Widget>[
Container(
key: Key(UniqueKey().toString()),
height: MediaQuery.of(context).size.height * 0.8,
child: FutureBuilder<List<dynamic>>(
future: catitems_api().fetchdeails(catHome, _currentPage),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return GridView.builder(
controller: scroll,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.7,
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
CategoryItems items = snapshot.data[index];
return InkWell(
onTap: () {
new productDet_api()
.fetchdeails(items.id)
.then((itemList) {
Navigator.of(context).push(PageRouteBuilder(
pageBuilder: (_, __, ___) =>
new detailProduk(itemList.first),
transitionDuration:
Duration(milliseconds: 950),
transitionsBuilder: (_flashSaleState,
Animation<double> animation,
__,
Widget child) {
return Opacity(
opacity: animation.value,
child: child,
);
}));
});
},
child: Container(
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black12.withOpacity(0.1),
spreadRadius: 0.2,
blurRadius: 5.0,
offset: Offset(0.0, 2.0))
]),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.network(
items.image,
width: 150.0,
height: 150.0,
fit: BoxFit.cover,
),
SizedBox(
height: 10.0,
),
Text(
items.name,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14.0,
color: Colors.black),
),
SizedBox(
height: 5.0,
),
Text(
"\$" + items.price.toString(),
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14.0,
color: Colors.black),
),
],
),
),
);
},
);
},
),
),
],
),
),
),
),
);
}
}

Related

Key in listView is causing rebuild in Flutter

class SupportScreen extends StatefulWidget {
const SupportScreen({Key? key}) : super(key: key);
#override
State<SupportScreen> createState() => _SupportScreenState();
}
class _SupportScreenState extends State<SupportScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final ScrollController _scrollController = ScrollController();
int? selected;
int page = 1;
#override
void initState() {
super.initState();
updateState();
}
updateState() {
_scrollController.addListener(() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent) {
setState(() {
page = page + 1;
});
BlocProvider.of<SupportBloc>(_scaffoldKey.currentState!.context)
.add(SupportGetFaqsWithPagination(page));
}
});
}
#override
void dispose() {
super.dispose();
_scrollController.dispose();
}
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SupportBloc(
SupportService(),
)..add(
SupportGetFaqsWithPagination(1),
),
child: Scaffold(
key: _scaffoldKey,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.only(
top: 16, bottom: 0, left: 16, right: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title(),
const SizedBox(height: 20),
BlocConsumer<SupportBloc, SupportState>(
listener: (context, state) {
if (state is SupportError) {
if (state.message.toString() == "401") {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(Constants
.sessionExpiredPleaseLoginAgain)));
Navigator.pushReplacementNamed(
context, AppRouter.loginPath);
}
}
},
builder: (context, state) {
return BlocBuilder<SupportBloc, SupportState>(
builder: (context, state) {
if (state is SupportLoaded) {
return cardView(state.faqs);
} else if (state is SupportLoading) {
return const Expanded(
child: Center(
child: CircularProgressIndicator(),
),
);
} else if (state is SupportInitial) {
return const SizedBox.shrink();
} else {
return Container();
}
});
},
),
],
),
),
)),
);
}
Widget cardView(results) {
return Expanded(
child: Container(
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: CommonFunctions.getThemeColor(context,
lightColor: ColorCode.colorWhite,
darkColor: ColorCode.textFieldDark)),
child: cardContainer(results),
),
);
}
Widget cardContainer(results) {
return ListView.builder(
controller: _scrollController,
key: Key('${selected.toString()}'),//here becuase of key it is causing rebuild
itemCount: results.length,
itemBuilder: (context, i) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
lists(i, results),
if (i != results.length - 1)
Padding(
padding: EdgeInsets.symmetric(horizontal: 15.0),
child: Divider(
color: CommonFunctions.getThemeColor(context,
lightColor: Color.fromRGBO(0, 0, 0, 0.4),
darkColor: Color.fromRGBO(229, 229, 229, 0.4))),
),
],
);
},
);
}
Text title() {
return const Text(
Constants.support,
style: TextStyle(
fontFamily: 'PublicSans-Medium',
fontWeight: FontWeight.w700,
fontSize: 20),
);
}
Theme lists(int i, results) {
return Theme(
data: ThemeData().copyWith(
dividerColor: Colors.transparent,
cardColor: Colors.white,
shadowColor: Colors.transparent,
primaryColor: ColorCode.secondaryColor),
child: ExpansionTile(
iconColor: ColorCode.secondaryColor,
collapsedIconColor: ColorCode.secondaryColor,
key: Key(i.toString()),
initiallyExpanded: i == selected,
title: Text(
results[i].question as String,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
fontFamily: 'PublicSans-Light',
color: CommonFunctions.getThemeColor(context,
lightColor: ColorCode.black,
darkColor: ColorCode.colorWhite)),
),
children: [
Padding(
padding: const EdgeInsets.only(left: 15.0, bottom: 15, right: 15),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
results[i].answer as String,
style: const TextStyle(
color: ColorCode.grey_600,
fontSize: 14,
fontFamily: 'PublicSans-Thin',
height: 1.5),
textAlign: TextAlign.start,
),
),
),
],
tilePadding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
onExpansionChanged: ((newState) {
if (newState) {
setState(() {
selected = i;
});
} else {
setState(() {
selected = -1;
});
}
})),
);
}
}
As soon I expand the Expansion tile the other expansion tile should collapse. It is happening because of the key given to ListView.builder. But as soon as selected value changes it rebuilds the page and the ListView jumps to top as soon as I expand the tiles in the bottom . I want only one Expansion tile to be open at a time and the listView should not jump to top. Here as I scroll I'm fetching the data from server and adding it end of the list. If I remove 'key' in ListView.builder i can open any number of expansion tiles but that is not the desirable output..How to solve this ?

Future builder return value keeps blinking

I have a screen with a container that shows the current time and under that a list view that contains list tiles with different regions and their corresponding time. I have a Future function that gets the time of a location. The problem is the text field that I have wrapped with future builder keeps blinking. It goes from null to time and back and forth. I want it to keep rebuilding to update the time, but how I do remove the blinking problem. and when I wrap newTime in setState it returns null. Any help will be really appreciated
class _WorldClockState extends State<WorldClock> {
final WorldTimeController worldTimeController =
Get.put(WorldTimeController());
String formattedTime = DateFormat('h:mm').format(DateTime.now());
String hour = DateFormat('a').format(DateTime.now());
late Timer _timer;
var location;
var time;
#override
void initState() {
super.initState();
_timer =
Timer.periodic(const Duration(milliseconds: 500), (timer) => _update());
}
void _update() {
setState(() {
formattedTime = DateFormat('h:mm').format(DateTime.now());
hour = DateFormat('a').format(DateTime.now());
});
}
var newUrl;
var newResponse;
getTime(location) async {
newUrl = "http://worldtimeapi.org/api/timezone/$location";
newResponse = await get(Uri.parse(newUrl));
Map newData = jsonDecode(newResponse.body);
var time = newData['datetime'];
String dateTime = newData["utc_datetime"];
String offset = newData["utc_offset"];
DateTime now = DateTime.parse(dateTime);
now = now.add(Duration(
hours: int.parse(offset.substring(1, 3)),
minutes: int.parse(offset.substring(4))));
String newtime = DateFormat.jm().format(now);
return newtime;
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: SingleChildScrollView(
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(formattedTime,
style: GoogleFonts.lato(
fontSize: 80.0,
color: Colors.blue,
fontStyle: FontStyle.normal,
letterSpacing: 5.0)),
Padding(
padding: const EdgeInsets.only(top: 40.0, left: 5.0),
child: Text(
hour,
style: GoogleFonts.lato(
color: Colors.blue,
fontSize: 30.0,
),
),
),
],
),
),
Container(
child: Text(
DateFormat('EE, MMM d').format(DateTime.now()),
style: GoogleFonts.lato(
color: Colors.white,
fontSize: 20.0,
letterSpacing: 2.0,
),
),
),
const Padding(
padding: EdgeInsets.only(top: 20.0, left: 30.0, right: 30.0),
child: Divider(
color: Colors.white,
height: 2.0,
),
),
Container(
height: 600.0,
width: 450.0,
child: Padding(
padding: const EdgeInsets.only(top: 20.0),
child: ListView.builder(
scrollDirection: Axis.vertical,
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
itemCount: worldTimeController.WorldTimeList.length,
itemBuilder: (BuildContext context, index) => Padding(
padding: const EdgeInsets.only(bottom: 18.0),
child: Slidable(
key: UniqueKey(),
startActionPane: ActionPane(
motion: ScrollMotion(),
dismissible: DismissiblePane(onDismissed: () {
worldTimeController.WorldTimeList.removeAt(index);
}),
children: const [],
),
child: ListTile(
leading: Text(
worldTimeController.WorldTimeList[index].location,
style: GoogleFonts.lato(
color: Colors.white70, fontSize: 25)),
trailing: FutureBuilder(
future: getTime(worldTimeController
.WorldTimeList[index].location),
builder: (context, AsyncSnapshot snapshot) {
return Text(
'${snapshot.data}',
style: GoogleFonts.lato(
color: Colors.white70, fontSize: 35.0),
);
}),
),
),
),
),
),
)
],
),
),
Replace your Future Func
FutureBuilder(
future: getTime(worldTimeController.WorldTimeList[index].location),
initialData:"",
builder: (context, AsyncSnapshot snapshot) {
return Text(
'${snapshot?.data??""}',
style: GoogleFonts.lato(
color: Colors.white70, fontSize: 35.0),
);
}),
I fixed the error by just removing the slidable widget that was wrapping the future builder, don't know why that was causing an error

How to animate a container on click in flutter

I am new to flutter animation, I have a stopwatch screen with a container that contains the stopwatch, a container with a list of laps, and 3 floating action buttons that do three things, reset, play-pause and lap. I want the stopwatch container to animate up when clicking on the lap button and then animate down when clicking on the reset button.
class _StopWatchScreenState extends State<StopWatchScreen>
with SingleTickerProviderStateMixin {
final StopWatchTimer _stopWatchTimer = StopWatchTimer();
final _isHours = true;
late AnimationController controller;
bool isPlaying = false;
bool lapClicked = false;
double value = 150.0;
final ScrollController scrollController = ScrollController();
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 200));
}
void dispose() {
super.dispose();
_stopWatchTimer.dispose();
scrollController.dispose();
controller.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: [
Center(
child: Padding(
padding: EdgeInsets.only(top: value), // Issue here
child: AnimatedContainer(
child: Center(
child: StreamBuilder<int>(
stream: _stopWatchTimer.rawTime,
initialData: _stopWatchTimer.rawTime.value,
builder: (context, snapshot) {
final value = snapshot.data;
final displayTime = StopWatchTimer.getDisplayTime(
value!,
hours: _isHours);
return Text(
displayTime,
style: GoogleFonts.lato(
fontSize: 40.0, color: Colors.white),
);
})),
width: 350.0,
height: 450.0,
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 5.0),
shape: BoxShape.circle),
duration: const Duration(milliseconds: 100),
curve: Curves.fastLinearToSlowEaseIn,
),
),
),
Visibility(
visible: lapClicked,
child: Container(
height: 280.0,
child: StreamBuilder<List<StopWatchRecord>>(
stream: _stopWatchTimer.records,
initialData: _stopWatchTimer.records.value,
builder: (context, snapshot) {
final value = snapshot.data;
if (value!.isEmpty) {
return Container();
}
Future.delayed(const Duration(milliseconds: 100), () {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 200),
curve: Curves.easeOut);
});
return ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: value.length,
itemBuilder: (context, index) {
final data = value[index];
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 20.0),
child: Text(
'Lap ${index + 1}',
style: GoogleFonts.lato(
fontSize: 30.0, color: Colors.white70),
),
),
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: Text('${data.displayTime}',
style: GoogleFonts.lato(
fontSize: 30.0,
color: Colors.white70)),
)
],
),
const Padding(
padding: EdgeInsets.only(top: 5.0),
child: Opacity(
opacity: 0.1,
child: Divider(
thickness: 1.5,
color: Colors.white70,
),
),
)
],
);
},
controller: scrollController,
);
},
),
),
),
],
),
floatingActionButton: Padding(
padding: const EdgeInsets.only(left: 30.0, bottom: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
FloatingActionButton(
onPressed: () {
setState(() {
lapClicked = false;
value = 190.0;
});
controller.reverse();
_stopWatchTimer.onExecute.add(StopWatchExecute.reset);
},
child: const Icon(
Icons.refresh,
size: 30.0,
),
),
FloatingActionButton(
onPressed: () {
toggleIcon();
if (isPlaying == true) {
_stopWatchTimer.onExecute.add(StopWatchExecute.start);
} else {
_stopWatchTimer.onExecute.add(StopWatchExecute.stop);
}
},
child: AnimatedIcon(
icon: AnimatedIcons.play_pause,
size: 35.0,
progress: controller,
),
),
FloatingActionButton(
onPressed: () {
setState(() {
lapClicked = true;
value = 10.0;
});
_stopWatchTimer.onExecute.add(StopWatchExecute.lap);
},
child: const Icon(
Icons.timer,
size: 30.0,
),
),
],
),
),
),
);
}
void toggleIcon() {
setState(() {
isPlaying = !isPlaying;
isPlaying ? controller.forward() : controller.reverse();
});
}
}
Use Animated Container with visibility
see this for animated container
Visibility(
visible:true,//controll to click on your lap button
child: AnimatedContainer(),
)
Animated Container
I used AnimatedContainer to animate the stopwatch and fixed the animation issue by removing the padding property from the container and replacing it with a margin property

flutter PageView onPageChanged with setstate

i am working with PageView but if i swipe to change it will change the page But if i put onPageChanged to setState for me to get to current index it will not change the page.
here is my code
int _indicatorsPages = 0;
final PageController controller =
PageController(initialPage: 0);
change(int page) {
setState(() {
_indicatorsPages = page;
}); }
code from build
Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
leading: Row(
children: [
SizedBox(
width: 10,
),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => SettingsFMC(),
),
);
},
child: Container(
height: 40,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
spreadRadius: 1, blurRadius: 2,
offset: Offset(0, 3), // i change position of shadow
),
],
),
child: Center(
child: Icon(
FontAwesomeIcons.slidersH,
size: 20,
color: Colors.black,
)),
),
),
],
),
elevation: 0,
backgroundColor: Colors.transparent,
actions: [
GestureDetector(
onTap: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Actitvities(),
),
);
final doc = await firestore
.collection('feeds')
.doc(auth.currentUser.uid)
.collection('feedsItems')
.get();
if (doc.docs.isNotEmpty) {
firestore
.collection('feeds')
.doc(auth.currentUser.uid)
.collection('feedsItems')
.get()
.then((value) {
value.docs.forEach((doc) {
doc.reference.update({'seen': true});
});
});
}
},
child: Container(
height: 40,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
spreadRadius: 1, blurRadius: 2,
offset: Offset(0, 3), // i change position of shadow
),
],
),
child: StreamBuilder(
stream: firestore
.collection('feeds')
.doc(auth.currentUser.uid)
.collection('feedsItems')
.where('seen', isEqualTo: false)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Icon(
Icons.notifications_none_outlined,
size: 20,
color: Colors.black,
));
}
if (snapshot.data.docs.isEmpty) {
return Center(
child: Icon(
Icons.notifications_none_outlined,
size: 20,
color: Colors.black,
));
}
return Badge(
animationType: BadgeAnimationType.scale,
badgeContent: Text('${snapshot.data.docs.length}'),
position: BadgePosition.topStart(),
showBadge: true,
child: Center(
child: Icon(
Icons.notifications_none_outlined,
size: 20,
color: Colors.black,
)),
);
}),
),
),
SizedBox(
width: 10,
),
],
),
extendBodyBehindAppBar: true,
body: userProfileLoading
? Center(
child: CircularProgressIndicator(),
)
: Stack(
children: [
Container(
height: MediaQuery.of(context).size.height / 1.6,
child: StreamBuilder(
stream: firestore
.collection('Image')
.doc(widget.viewId)
.collection('Photos')
.orderBy('timestap', descending: false)
.snapshots(),
builder: (context, AsyncSnapshot snapshot) {
int idx = 0;
List<Widget> list = [];
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (!snapshot.hasData) {
return Center(
child: Text("No image Found, Add images"),
);
} else {
if (snapshot.hasError) {
return Center(child: Text("fetch error"));
} else {
for (int i = 0;
i < snapshot.data.docs.length;
i++) {
// print('the lent of the document is $idx');
list.add(
FullScreenWidget(
child: Hero(
tag: "customTag",
child: Image.network(
snapshot.data.docs[idx]
.data()['picture'],
fit: BoxFit.cover,
),
),
),
);
idx++;
}
return Stack(
children: [
PageView(
key: _key,
scrollDirection: Axis.horizontal,
controller: controller,
onPageChanged: change,
// onImageChange: (pre, current) {
// print('this current : $current');
// setState(() {
// indicatorsPages = current;
// });
// },
// boxFit: BoxFit.cover,
// autoplay: false,
// animationCurve: Curves.fastOutSlowIn,
// animationDuration:
// Duration(milliseconds: 1000),
// dotIncreasedColor: Colors.orange,
// dotBgColor: Colors.transparent,
// dotPosition: DotPosition.bottomCenter,
// dotVerticalPadding:
// MediaQuery.of(context).size.height / 15,
//showIndicator: false,
// indicatorBgPadding: 7.0,
children: list,
),
Positioned(
right:
MediaQuery.of(context).size.width / 2,
bottom: 75,
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(20),
color: Colors.white),
child: indicat.DotsIndicator(
dotsCount: list.length,
position: _indicatorsPages.toDouble(),
decorator: DotsDecorator(
color:
Colors.grey, // Inactive color
activeColor: Colors.black,
),
)),
)
],
);
}
}
}),
),
You are wrong. When you use pageView, it will call onPageChnaged function after page changed. If you want to change page programmatically, you should use pageView.animateToPage() function.
import 'package:flutter/material.dart';
class OnBoarding extends StatefulWidget {
#override
_OnBoardingState createState() => _OnBoardingState();
}
class _OnBoardingState extends State<OnBoarding> {
PageController controller;
int currentIndex = 0;
#override
void initState() {
controller = PageController();
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueAccent[200],
body: Stack(
children: [
PageView(
onPageChanged: onchahged,
controller: controller,
children: [
Container(
child: Image.asset('assets/fierceninja.png'),
),
Container(
child: Image.asset('assets/ninja.png'),
),
Container(
child: Image.asset('assets/ninjahead.jpg'),
),
],
),
],
),
);
}
onchahged(int index) {
setState(() {
currentIndex = index;
});
}
}
Here's a complete example.

setState not rebuilding the widget

I'm new to Flutter/Dart developing but i'm having this issue where calling setState is not rebuilding the widget.
I'm trying to show in the UI the genres picked by the user when tapping on them as well as adding them to another list. The color of the tapped selection is supposed to change yet when reopening the modal nothing changes but the genre is actually added to another list.
Here's the code
class GenresPicker extends StatefulWidget {
#override
_GenresPickerState createState() => _GenresPickerState();
}
class _GenresPickerState extends State<GenresPicker> {
List<Genre> _selectedGenres = [];
#override
void initState() {
super.initState();
}
addGenre(genre) {
setState(() {
_selectedGenres.add(genre);
});
}
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
padding: EdgeInsets.all(20),
child: FutureBuilder<List<Genre>>(
future: fetchGenres(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"MODAL BOTTOM SHEET EXAMPLE",
style: TextStyle(
fontStyle: FontStyle.italic,
letterSpacing: 0.4,
fontWeight: FontWeight.w600),
),
SizedBox(
height: 20,
),
ElevatedButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
return Column(
children: <Widget>[
Text("Genres:"),
Expanded(
child: GridView.builder(
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100,
childAspectRatio: 4 / 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext ctx, index) {
return Container(
alignment: Alignment.center,
child: GestureDetector(
onTap: () {
//_selectedGenres
// .add(snapshot.data![index]);
if (!_selectedGenres.contains(
snapshot.data![index])) {
addGenre(snapshot.data![index]);
}
},
child: Text(snapshot.data![index].name),
),
decoration: BoxDecoration(
color: _selectedGenres.contains(
snapshot.data![index])
? Colors.blue[200]
: Colors.blue[50],
borderRadius:
BorderRadius.circular(15)),
);
},
),
),
],
);
},
);
},
child: Text(
'Click Me',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
letterSpacing: 0.6),
),
),
],
),
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return Center(child: CircularProgressIndicator());
},
),
);
}
}
The commented code was a workoround that somehow worked better but not the intended result since with it when tapping and reopening the modal the selection is actually shown.
Thanks in advance.
This happens just because you use setState inside the showModalBottomSheet(Dialog).
Make sure whenever you want to refresh data inside the dialog wrap the child with StatefulBuilder and use that builder's setState to refresh the list it will work.
Check the example
import 'package:flutter/material.dart';
class BottomRefreshExample extends StatefulWidget {
#override
_BottomRefreshExampleState createState() => _BottomRefreshExampleState();
}
class _BottomRefreshExampleState extends State<BottomRefreshExample> {
List<String> _selectedListData = [];
List<String> _responseListData = ["Add1", "Add2", "Add3", "Add4", "Add5"];
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
padding: EdgeInsets.all(20),
child: Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"MODAL BOTTOM SHEET EXAMPLE",
style: TextStyle(fontStyle: FontStyle.italic, letterSpacing: 0.4, fontWeight: FontWeight.w600, fontSize: 12),
),
SizedBox(
height: 20,
),
ElevatedButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(builder: (context, setNewState) {
return Column(
children: <Widget>[
Expanded(
child: GridView.builder(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100, childAspectRatio: 4 / 2, crossAxisSpacing: 10, mainAxisSpacing: 10),
itemCount: _responseListData.length,
itemBuilder: (BuildContext ctx, index) {
return Container(
alignment: Alignment.center,
child: GestureDetector(
onTap: () {
if (!_selectedListData.contains(_responseListData[index])) {
setNewState(() {
_selectedListData.add(_responseListData[index]);
});
}
},
child: Text(_responseListData[index]),
),
decoration: BoxDecoration(
color: _selectedListData.contains(_responseListData[index]) ? Colors.blue[200] : Colors.blue[50],
borderRadius: BorderRadius.circular(15)),
);
},
),
),
],
);
});
},
);
},
child: Text(
'Open Bottom Sheet',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w600, letterSpacing: 0.6),
),
),
],
),
),
);
}
}