setState() or markNeedsBuild() called during build on ListView - flutter

So I am trying to refactor my listView logic. Basically my ListView has become cumbersome with the UI logic , so I decided, why not move certain parts of the UI logic to another class
This is my code
ListPage.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:sample_flutter_works/ListTextArea.dart';
import 'package:sample_flutter_works/Model.dart';
import 'dart:convert';
import 'package:sample_flutter_works/RefreshTableContainer.dart';
class ListPage extends StatefulWidget {
#override
MyListPage createState() => MyListPage();
}
class MyListPage extends State<ListPage> {
MessageList messageList;
List<int> viewTimeInfo;
ScrollController _controller;
_scrollListener() {
}
#override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
_controller = ScrollController();
_controller.addListener(_scrollListener);
loadMessages(
completionBlock: (dataSet) => {
setState(() {
messageList = dataSet;
})
});
}
void loadMessages({completionBlock}) async {
var jsonString = await rootBundle.loadString('assets/Chat.json');
final jsonResponse = json.decode(jsonString);
if (jsonResponse != null) {
completionBlock(MessageList.fromJSON(jsonResponse));
} else {
completionBlock(null);
}
}
Widget listLayout() {
return ListView.separated(
padding: const EdgeInsets.all(8.0),
itemCount: (messageList != null && messageList.msgList != null)
? messageList.msgList.length
: 0,
separatorBuilder: (context, index) => Divider(
color: Colors.black,
height: 4.0,
),
itemBuilder: (BuildContext context, int index) {
var msgValToSend =
(messageList != null && messageList.msgList != null)
? messageList.msgList[index]
: null;
return Stack(
children: <Widget>[
IntrinsicHeight(
child: Row(
children: <Widget>[
getTheImageLayout(msgValToSend),
new ListTextArea(
msg: msgValToSend,
didTapOnTextArea: tappedOnTextArea,
visibilityCheck: checkForVisibility)
],
),
)
],
);
});
}
tappedOnTextArea(Message msg) {
var viewedInfo = this.viewTimeInfo;
if (viewedInfo != null) {
var indexOfTappedElement = viewedInfo.indexOf(msg.messageID);
if (indexOfTappedElement != null && indexOfTappedElement != -1) {
viewedInfo.removeAt(indexOfTappedElement);
} else {
viewedInfo.add(msg.messageID);
}
} else {
viewedInfo = [msg.messageID];
}
setState(() {
viewTimeInfo = viewedInfo;
});
}
checkForVisibility(bool _visible, Message msg) {
if (msg != null && this.viewTimeInfo != null) {
var checkForIndex = this.viewTimeInfo.indexOf(msg.messageID);
if (checkForIndex != null && checkForIndex != -1) {
_visible = true;
}
}
}
Widget getTheImageLayout(Message msg) {
return Expanded(
flex: 2,
child: Align(
alignment: Alignment.topLeft,
child: Padding(
padding: EdgeInsets.fromLTRB(5, 2.5, 0, 0),
child: Container(
color: Colors.red,
height: 50,
child: Row(
children: <Widget>[
userImageView(msg),
],
)),
)));
}
Widget userImageView(Message msg) {
return Expanded(
flex: 8,
child: Align(
alignment: Alignment.centerLeft,
child: Container(
width: 40.0,
height: 40.0,
decoration:
BoxDecoration(shape: BoxShape.circle, color: Colors.green),
child: ClipOval(
child: Image.network(
(msg.msgUser.userPicUrl != null)
? msg.msgUser.userPicUrl
: 'https://picsum.photos/250?image=9',
fit: BoxFit.fill,
),
))));
}
Future<void> refreshTheChatTable() async {
print(" This is where the logic of pulll 2 refresh must be written ");
loadMessages(
completionBlock: (dataSet) => {
setState(() {
messageList = dataSet;
})
});
}
#override
Widget build(BuildContext context) {
return new RefreshTableContainer(
listLayout: listLayout(),
pull2RefreshAction: refreshTheChatTable,
);
}
}
ListTextArea.dart
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:sample_flutter_works/Model.dart';
class ListTextArea extends StatelessWidget {
Message msg;
Function didTapOnTextArea;
Function visibilityCheck;
ListTextArea({
this.msg,
this.didTapOnTextArea,
this.visibilityCheck
});
#override
Widget build(BuildContext context) {
return Expanded(
flex: 8,
child: GestureDetector(
onTap: didTapOnTextArea(msg),
child: Padding(
padding: EdgeInsets.fromLTRB(0, 2.5, 10, 0),
child: Column(
children: getChildWidgetArray(msg) ,
),
),
));
}
List<Widget> getChildWidgetArray(Message msg) {
var elementalArray = [
Align(
alignment: Alignment.topLeft,
child: Text(
(msg != null) ? msg.msgContent.content : "Data Loading",
style: TextStyle(
background: Paint()..color = Colors.orange,
),
),
),
Spacer(), // Defaults to a flex of one.
Align(
alignment: Alignment.bottomRight,
child: Text(
'Date of sending',
textDirection: TextDirection.rtl,
style: TextStyle(
background: Paint()..color = Colors.blue,
),
),
)
];
var _visible = false;
visibilityCheck(_visible,msg);
var timeInfo = AnimatedOpacity (
opacity: _visible ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Align(
child: _visible ? (Align(alignment: Alignment.topLeft,child:Column(children: <Widget>[Text("Last Read :" + (msg.msgTimeInfo.lastReadInfo)),
Text("Delievered :" + (msg.msgTimeInfo.deliveredInfo))],))): null));
elementalArray.add(timeInfo);
return elementalArray;
}
}
The error is as follows:
What I am trying to do ( or had done earlier on when the entire code was in ListPage.dart ) was dynamically calculated cells in a listView, each cell responding to a tap action that shows in more data. I don't understand what I did wrong here at all.
I called the setState in init but inside a callback function. The statelesswidget ListTextArea will not handle the state at all, but returns the tapAction to the StateFulWidget ListPage.dart.
So why am I getting this error. Any insights would be helpful.

In my case, the error occurred when I was setting the state before build was complete, so, I deferred it to the next tick and it worked.
previously
myFunction()
New
Future.delayed(Duration.zero, () async {
myFunction();
});

The problem is in ListTextArea.dart, line
onTap: didTapOnTextArea(msg),
You are calling function didTapOnTextArea in build method instead of passing it as tap listener. You have to replace it with
onTap: (){
didTapOnTextArea(msg);
},

Related

FLUTTER : How to only rebuild the top page in stack flutter

I have an issue with rebuilding pages in the stack with Flutter
This is all my users and have added the search to the top appBar.
But it works with conditions to see if it is widgets there then get height of the widgets that is being fixed underneath the appBar...But that happens asynchronously.
So when firstLoad it works but when I call setState it then rebuilds all the pages in the stack and with that it looks like this
This is how it looks after a set state. The problem i saw is that the previous pages have an influence. I couldn't find a good viable solution to this. Will explain my architecture.
I have a page Layout that is a container wrapper for all my pages that has it's appBar styles and just sends through the children. But that is the page Layout wrapper that is being rebuild every time the a set States happen
HOW I GET MY WIDGET SIZE
HOW I IMPLEMENTED IT
It goes in the else with the other pages in the stack.. I tried putting it in the initState but it never goes inside because it is used in the other pages in the stack
I only need an implementation to rebuild the TOP page in the stack.
PAGE LAYOUT
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:flutterweb/constants.dart';
import 'package:flutterweb/controllers/channel_controller.dart';
import 'package:flutterweb/controllers/user_controller.dart';
import 'package:flutterweb/main.dart';
import 'package:flutterweb/models/channels_model.dart';
import 'package:flutterweb/utils/functions.dart';
import 'package:flutterweb/views/channels/func.dart';
import 'package:flutterweb/views/home/home.dart';
import 'package:flutterweb/views/menu/permissions/choose_assign_group.dart';
import 'package:flutterweb/widgets/builders/KNetworkFadeImage.dart';
import 'package:flutterweb/widgets/builders/kPopups.dart';
import 'package:flutterweb/widgets/drawerDara.dart';
import 'package:get/get.dart';
class CustomAppBar extends StatefulWidget {
final String title;
final List<Map<String, dynamic>>? topTabs;
final TabController? topTabController;
final List<Widget>? children;
final List<Widget>? childrenFixed;
final Function? leftActionFunction;
final Icon? leftActionIcon;
final Drawer? drawer;
final Function? logOutPressed;
final bool showOptionsMenu;
final Widget? optionMenu;
final Widget? bottomNavigationBar;
final String? backGroundImage;
final ScrollController? scrollController;
CustomAppBar({
required this.title,
this.topTabs,
this.topTabController,
this.leftActionFunction,
this.leftActionIcon,
this.children,
this.childrenFixed,
this.drawer,
this.logOutPressed,
this.showOptionsMenu = false,
this.optionMenu,
this.bottomNavigationBar,
this.backGroundImage,
this.scrollController,
});
#override
_CustomAppBarState createState() => _CustomAppBarState();
}
double app_content_height = 0;
double fixedWidgetSize = 0;
String prevTitle = "";
class _CustomAppBarState extends State<CustomAppBar>
with SingleTickerProviderStateMixin {
final GlobalKey<ScaffoldState> scaffoldkey = GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
_toggleAnimation() {
scaffoldkey.currentState!.openDrawer();
}
double _getAppBarSize() {
double fixedHeightInclude = fixedWidgetSize;
if (widget.topTabs != null) {
fixedHeightInclude += 100;
} else if (widget.title == "") {
fixedHeightInclude += 0;
} else {
fixedHeightInclude += 60;
}
return fixedHeightInclude;
}
#override
Widget build(BuildContext context) {
// if (widget.title != global_title) return SizedBox();
List<Widget> arr = [];
Widget arrView = SizedBox();
double statusBar = 0;
double _width = MediaQuery.of(context).size.width;
Widget? fixedChild = SizedBox();
if ((widget.childrenFixed?.length ?? 0) > 1) {
fixedChild = WidgetSize(
child: Column(children: widget.childrenFixed!),
onChange: (Size size) {
fixedWidgetSize = 0;
setState(() {
fixedWidgetSize = size.height;
});
kPrint("fixedWidgetSize ${size.height}");
},
);
} else {
fixedWidgetSize = 0;
}
// Widget? fixedChild = (widget.childrenFixed?.length ?? 0) > 1
// ? WidgetSize(
// child: Column(children: widget.childrenFixed!),
// onChange: (Size size) {
// fixedWidgetSize = 0;
// setState(() {
// fixedWidgetSize = size.height;
// });
// kPrint("fixedWidgetSize ${size.height}");
// },
// )
// : SizedBox();
app_content_height =
MediaQuery.of(context).size.height - _getAppBarSize() - statusBar;
if (widget.title != "") {
arr.add(
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 2,
child: widget.showOptionsMenu == true
? IconButton(
icon: const Icon(Icons.menu, color: Colors.white),
onPressed: () => _toggleAnimation(),
)
: IconButton(
icon: widget.leftActionIcon ??
const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
if (widget.leftActionFunction != null) {
widget.leftActionFunction!();
} else {
if (Navigator.canPop(context)) {
Get.back();
}
}
},
),
),
Expanded(flex: 2, child: SizedBox()),
Expanded(
flex: 10,
child: Center(
child: Text(
widget.title,
style: const TextStyle(color: Colors.white, fontSize: 24.0),
),
),
),
Expanded(
flex: 4,
child: widget.logOutPressed != null
? IconButton(
icon: const Icon(Icons.power_settings_new_outlined,
color: Colors.white),
onPressed: () {
widget.logOutPressed!();
},
)
: widget.optionMenu ?? Container(),
),
],
),
);
}
if (widget.topTabs != null) {
List<Widget> tempTopBar = [];
List<Widget> tempTopView = [];
for (var i = 0; i < widget.topTabs!.length; i++) {
String key = widget.topTabs![i].keys
.toString()
.replaceAll("(", "")
.replaceAll(")", "");
Widget value = widget.topTabs![i][key];
tempTopBar.add(Tab(text: key));
tempTopView.add(SingleChildScrollView(child: value));
}
arr.add(
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Center(
child: Container(
height: 30,
child: DefaultTabController(
length: widget.topTabs!.length,
child: TabBar(
labelPadding: widget.topTabs!.length == 2
? const EdgeInsets.symmetric(horizontal: 40.0)
: const EdgeInsets.symmetric(horizontal: 16.0),
controller: widget.topTabController,
indicatorSize: TabBarIndicatorSize.tab,
indicator: CircleTabIndicator(color: Colors.white, radius: 4),
isScrollable: true,
labelColor: Colors.white,
tabs: tempTopBar,
),
),
),
),
),
);
// arr.add(child);
arrView = Container(
width: _width,
height: app_content_height,
child: TabBarView(
controller: widget.topTabController,
children: tempTopView,
),
);
}
if (widget.children != null) {
arrView = Container(
width: _width,
height: app_content_height,
child: ListView(
// controller: widget.scrollController ?? ScrollController(),
children: widget.children!,
),
);
}
_getStatus() {
if (statusBar > 0) {
Color color = AppColors.kBlue;
return Container(
height: Get.height * 0.03,
width: Get.width,
color: color,
child: const Center(
child: Text(
"",
style: const TextStyle(color: Colors.black),
),
),
);
} else {
return const SizedBox();
}
}
return SafeArea(
child: Material(
child: Stack(
children: [
Scaffold(
resizeToAvoidBottomInset:
true, //That the keyboard shows correctly
extendBodyBehindAppBar: true,
key: scaffoldkey,
appBar: PreferredSize(
preferredSize: Size.fromHeight(
_getAppBarSize()), // here the desired height
child: Container(
decoration: kAppBarBoxDecorations,
child: Column(
children: [
Column(
children: arr,
),
fixedChild,
],
),
),
),
drawer: Drawer(
child: ListView(
children: <Widget>[
UserAccountsDrawerHeader(
accountName: InkWell(
onTap: () {
Channels element =
ChannelController.to.gSelectedChannel.value;
getChannelRoles(element);
},
child: Text(
"${ChannelController.to.gSelectedChannel.value.rolDesc} >",
),
),
accountEmail: Text(
UserController.to.gUserModel.value.email.toString()),
currentAccountPicture: GestureDetector(
child: const CircleAvatar(
backgroundImage: NetworkImage(
"https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500"),
),
onTap: () => print("Current User")),
decoration: const BoxDecoration(
image: DecorationImage(
fit: BoxFit.fill,
image: NetworkImage(
"${URLS.keyBaseUrl}/assets/images/background/background7.jpg")),
),
),
ListTile(
title: const Text("Home"),
trailing: const Icon(Icons.home),
onTap: () =>
Get.toNamed(Home.router, preventDuplicates: false),
),
ListTile(
title: const Text("Menu Permissions"),
trailing: const Icon(Icons.home),
onTap: () => Get.toNamed(ChooseAssignGroup.router,
preventDuplicates: false),
),
const Divider(
thickness: 1.0,
),
drawerData(),
const Divider(
thickness: 1.0,
),
ListTile(
title: const Text("Close"),
trailing: const Icon(Icons.cancel),
onTap: () => Navigator.of(context).pop(),
),
ListTile(
title: const Text("Log Out"),
trailing: const Icon(Icons.logout),
onTap: () => UserController.to.logOutUser(),
),
],
),
),
body: Container(
decoration: widget.backGroundImage != null
? BoxDecoration(
color: Colors.black.withOpacity(0.9),
image: DecorationImage(
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.2), BlendMode.dstATop),
image: NetworkImage(widget.backGroundImage!),
),
)
: BoxDecoration(color: Colors.grey.shade400),
child: Center(
child: Container(
constraints: BoxConstraints(maxWidth: 800),
padding: EdgeInsets.only(
left: 15.0, right: 15.0, top: _getAppBarSize()),
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: arrView,
),
),
),
),
bottomNavigationBar: widget.bottomNavigationBar,
),
],
),
),
);
}
}
class CircleTabIndicator extends Decoration {
final BoxPainter _painter;
CircleTabIndicator({required Color color, required double radius})
: _painter = _CirclePainter(color, radius);
#override
BoxPainter createBoxPainter([onChanged()?]) => _painter;
}
class _CirclePainter extends BoxPainter {
final Paint _paint;
final double radius;
_CirclePainter(Color color, this.radius)
: _paint = Paint()
..color = color
..isAntiAlias = true;
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration cfg) {
final Offset circleOffset =
offset + Offset(cfg.size!.width / 2, cfg.size!.height - radius);
canvas.drawCircle(circleOffset, radius, _paint);
}
}
class WidgetSize extends StatefulWidget {
final Widget child;
final Function onChange;
const WidgetSize({
required this.onChange,
required this.child,
});
#override
_WidgetSizeState createState() => _WidgetSizeState();
}
class _WidgetSizeState extends State<WidgetSize> {
#override
Widget build(BuildContext context) {
SchedulerBinding.instance.addPostFrameCallback(postFrameCallback);
return Container(
key: widgetKey,
child: widget.child,
);
}
var widgetKey = GlobalKey();
var oldSize;
void postFrameCallback(_) {
var context = widgetKey.currentContext;
if (context == null) return;
var newSize = context.size;
if (oldSize == newSize) return;
oldSize = newSize;
widget.onChange(newSize);
}
}
ALL VERIFIED USERS
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutterweb/controllers/channel_controller.dart';
import 'package:flutterweb/controllers/user_controller.dart';
import 'package:flutterweb/main.dart';
import 'package:flutterweb/models/user_model.dart';
import 'package:flutterweb/thirdParty/googleSignin.dart';
import 'package:flutterweb/utils/functions.dart';
import 'package:flutterweb/views/menu/permissions/menu_assign.dart';
import 'package:flutterweb/widgets/builders/kPopups.dart';
import 'package:flutterweb/widgets/buttons/KIconOnlyButton.dart';
import 'package:flutterweb/widgets/builders/KNetworkFadeImage.dart';
import 'package:flutterweb/widgets/builders/customAppBar.dart';
import 'package:flutterweb/widgets/buttons/KButton.dart';
import 'package:flutterweb/constants.dart';
import 'package:flutterweb/widgets/cards/KStudentInfoCard.dart';
import 'package:flutterweb/widgets/cards/kStudentCard.dart';
import 'package:flutterweb/widgets/input/KInputBar.dart';
import 'package:flutterweb/widgets/input/KTextField.dart';
import 'package:flutterweb/widgets/static/kLabel.dart';
import 'package:flutterweb/widgets/text/kInfo.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
class AllVerifiedUsers extends StatefulWidget {
static const String router = "/allVerifiedUsers";
AllVerifiedUsers({Key? key}) : super(key: key);
#override
_MyPageState createState() => _MyPageState();
}
// The controller for the ListView
late ScrollController _controller;
class _MyPageState extends State<AllVerifiedUsers> {
// The controller for the ListView
late ScrollController _controllerTest;
int _page = 1;
final int _limit = 20;
bool _hasNextPage = true;
bool _isFirstLoadRunning = false;
bool _isLoadMoreRunning = false;
List<UserModel> _posts = [];
String searchVal = "";
void _firstLoad() async {
setState(() {
_isFirstLoadRunning = true;
});
try {
List<UserModel> lUserMode = await ChannelController.to
.fetchUsersChannels(_limit, _page, searchVal);
setState(() {
_posts = lUserMode;
});
} catch (err) {
kPrint('Something went wrong');
}
setState(() {
_isFirstLoadRunning = false;
});
}
void _loadMore() async {
if (_isFirstLoadRunning == false &&
_isLoadMoreRunning == false &&
_controller.position.extentAfter < 300) {
setState(() {
_isLoadMoreRunning = true; // Display a progress indicator at the bottom
});
_page += 1; // Increase _page by 1
try {
List<UserModel> lUserMode = await ChannelController.to
.fetchUsersChannels(_limit, _page, searchVal);
if (lUserMode.isNotEmpty) {
setState(() {
_hasNextPage = true;
_posts.addAll(lUserMode);
});
} else {
// This means there is no more data
// and therefore, we will not send another GET request
setState(() {
_hasNextPage = false;
});
}
} catch (err) {
print('Something went wrong!');
}
setState(() {
_isLoadMoreRunning = false;
});
}
}
#override
void initState() {
super.initState();
_firstLoad();
_controller = ScrollController()..addListener(_loadMore);
_controllerTest = ScrollController()
..addListener(() => {kPrint("CustomView Scroll")});
}
#override
void dispose() {
super.dispose();
_controller.removeListener(_loadMore);
}
#override
Widget build(BuildContext context) {
global_title = "All Verified Users";
return CustomAppBar(
title: "All Verified Users",
scrollController: _controllerTest,
childrenFixed: [
kAddSpace(2),
CustomInputBar(
inverse: true,
title: "Search",
onChanged: (String value) {
if (value == "") {
_firstLoad();
return;
}
setState(() {
searchVal = value;
_posts = [];
});
_loadMore();
},
),
],
children: [
kAddSpace(2),
KLabel(
label: "Choose Verified User",
),
kAddSpace(2),
_isFirstLoadRunning
? const Center(
child: CircularProgressIndicator(),
)
: Column(
children: [
SizedBox(
height: app_content_height,
// width: Get.width,
child: ListView.builder(
shrinkWrap: true,
controller: _controller,
itemCount: _posts.length,
itemBuilder: (_, index) {
UserModel item = _posts[index];
return KStudentCard(
imgUrl: "",
onPressed: () {
ChannelController.to.gSelectedMenuUserModel.value =
item;
Get.toNamed(MenuAssign.router);
},
name: "${item.name} ${item.surname}",
);
},
),
),
// when the _loadMore function is running
if (_isLoadMoreRunning == true)
const Padding(
padding: EdgeInsets.only(top: 10, bottom: 40),
child: Center(
child: CircularProgressIndicator(),
),
),
// When nothing else to load
if (_hasNextPage == false)
Container(
padding: const EdgeInsets.only(top: 30, bottom: 40),
color: Colors.amber,
child: const Center(
child: Text('You have fetched all of the content'),
),
),
],
),
kAddSpace(2),
],
);
}
}
Thank you
As I understand it, the idea is a fixed TextArea and a scrollable List beneath. A solution that works without computing any height would be:
final items = List<String>.generate(1000, (i) => 'Item $i');
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
//
// TEXTBOX
//
Container(
color: Colors.red,
child: Padding(
padding: const EdgeInsets.all(16),
child: TextFormField(
// controller: controller,
))),
const SizedBox(height: 16),
//
// LIST
//
Expanded(
child: ListView.builder(
shrinkWrap: true, // IMPORTANT
itemCount: items.length,
itemBuilder: (context, index) {
return Text(items[index]);
})),
]),
);
}

Scroll end detect to many times in flutter

I uses Listview.builder. it detect scroll end to many time that is why API call to many times and add duplicate Data in Listview.
Code:-
ListView.builder(
controller: _scrollController
..addListener(() async {
if (_scrollController
.position.pixels -
10 ==
_scrollController.position
.maxScrollExtent -
10 &&
!state.isPaginationLoading) {
print("Scroll End TEst Screen");
await ctx
.read<ProfileCubit>()
.getProfiles(
context, true, null);
}
Dont put logic code inside build. In your case _scrollController will addListener every times widget build called, cause multiple handle will trigger.
Advice for you is create and put handle logic to a function, put addListener/removeListener in initState/dispose because they was called only once.
With your problem, you can create a variale to check api was called yet and prevent other call.
class AppState extends State<App> {
var scroll = ScrollController();
var preventCall = false;
#override
initState() {
scroll.addListener(onScroll);
super.initState();
}
#override
void dispose() {
scroll.removeListener(onScroll);
super.dispose();
}
Future yourFuture() async {}
void onScroll() {
var position = scroll.position.pixels;
if (position >= scroll.position.maxScrollExtent - 10) {
if (!preventCall) {
yourFuture().then((_) => preventCall = false);
preventCall = true;
}
}
}
#override
Widget build(BuildContext context) {
return ...
}
}
You can add a condition to check if API call is happening or not and based on it you can you can call the API. You would also need to handle pagination logic if all info is loaded.
you can always check if you reached at the limit of max Limit of your scroll controller then you can call API
condition is like
child: ListView.builder(
controller: _controller
..addListener(() async {
if (_controller.position.pixels >
_controller.position.maxScrollExtent) {
_loadMore();
}
}),
you may be like full example
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
class Paggination2HomeScreen extends StatefulWidget {
const Paggination2HomeScreen({Key? key}) : super(key: key);
#override
State<Paggination2HomeScreen> createState() => _Paggination2HomeScreenState();
}
class _Paggination2HomeScreenState extends State<Paggination2HomeScreen> {
final String _baseUrl = "https://jsonplaceholder.typicode.com/photos";
int _page = 1;
final int _limit = 10;
bool _hasNextPage = true;
bool _isFirstLoadRunning = false;
bool _isLoadMoreRunning = false;
List _posts = [];
void _firstLoad() async {
setState(() {
_isFirstLoadRunning = true;
});
try {
final res = await http.get(
Uri.parse("$_baseUrl?_page=$_page&_limit=$_limit"),
);
if (res.statusCode == 200) {
setState(() {
_posts = jsonDecode(res.body);
});
log('receivedData : ${jsonDecode(res.body)}');
}
} catch (e) {
if (kDebugMode) {
log('Something went wrong');
}
}
setState(() {
_isFirstLoadRunning = false;
});
}
void _loadMore() async {
if (_hasNextPage == true &&
_isFirstLoadRunning == false &&
_isLoadMoreRunning == false) {
setState(() {
_isLoadMoreRunning = true;
});
_page++;
try {
final res = await http.get(
Uri.parse('$_baseUrl?_page=$_page&_limit=$_limit'),
);
log('url : $_baseUrl?_page=$_page&_limit=$_limit');
if (res.statusCode == 200) {
final List fetchedPost = jsonDecode(res.body);
if (fetchedPost.isNotEmpty) {
setState(() {
_posts.addAll(fetchedPost);
});
} else {
setState(() {
_hasNextPage = false;
});
}
}
} catch (e) {
if (kDebugMode) {
log('something went wrong');
}
}
setState(() {
_isLoadMoreRunning = false;
});
}
}
// the controller for listyView
late ScrollController _controller;
#override
void initState() {
super.initState();
_firstLoad();
_controller = ScrollController();
}
#override
void dispose() {
super.dispose();
_controller.removeListener(_loadMore);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.orangeAccent,
elevation: 0.0,
centerTitle: true,
title: Text(
'Paggination',
style: TextStyle(
color: Colors.black.withOpacity(0.52),
fontSize: 20,
),
),
systemOverlayStyle: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
),
),
body: _isFirstLoadRunning
? Center(
child: CircularProgressIndicator(
color: Colors.black.withOpacity(0.25),
),
)
: Column(
children: [
Expanded(
child: ListView.builder(
controller: _controller
..addListener(() async {
if (_controller.position.pixels >
_controller.position.maxScrollExtent) {
_loadMore();
}
}),
physics: const BouncingScrollPhysics(),
itemCount: _posts.length,
itemBuilder: (context, index) => Container(
padding: const EdgeInsets.all(10),
margin: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.065),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.black.withOpacity(0.2),
width: 0.5,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'ID : ${_posts.elementAt(index)['id']}',
),
Text(
'AlbumID : ${_posts.elementAt(index)['albumId']}',
),
Text(
'Title : ${_posts.elementAt(index)['title']}',
textAlign: TextAlign.justify,
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
height: MediaQuery.of(context).size.width * 0.4,
width: MediaQuery.of(context).size.width * 0.4,
child: Image.network(
_posts.elementAt(index)['url'],
),
),
SizedBox(
height: MediaQuery.of(context).size.width * 0.4,
width: MediaQuery.of(context).size.width * 0.4,
child: Image.network(
_posts.elementAt(index)['thumbnailUrl'],
),
),
],
),
],
),
),
),
),
// when the _loadMore running
if (_isLoadMoreRunning == true)
Container(
color: Colors.transparent,
padding: const EdgeInsets.only(top: 10, bottom: 20),
child: const Center(
child: CircularProgressIndicator(
color: Colors.amberAccent,
strokeWidth: 3,
backgroundColor: Colors.transparent,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
),
),
),
if (_hasNextPage == false)
SafeArea(
child: Container(
width: double.maxFinite,
padding: const EdgeInsets.only(top: 20, bottom: 20),
color: Colors.orangeAccent,
child: const Text('you get all'),
),
)
],
),
);
}
}

SingleChildScrollView doesn't work on web, only in mobile

i'm trying to create a horizontaly scrollable list in Flutter with SingleChildScrollView but, only works in mobile, i tried another solutions but nothing worked for me. I'm brazilian, and some parts of the code are in portuguese.
The widget is rendered in another scroll view, i'ts work like the Netflix movies list in web...
Here is the code:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:t2_market/src/Controllers/ConfiguracaoController.dart';
import 'package:t2_market/src/Controllers/GrupoMarcaController.dart';
import 'package:t2_market/src/Model/Configuracao/Configuracao.dart';
import 'package:t2_market/src/Templates/Components/LoaderShimmers.dart';
import 'package:t2_market/src/Templates/Mobile/Categories/BrandGroupList.dart';
import 'package:t2_market/src/Templates/Mobile/Categories/CategoriesListWeb.dart';
import 'package:t2_market/src/core/AppTextStyles.dart';
import 'dart:async';
StreamController<String> streamController =
StreamController<String>.broadcast();
class Categories extends StatefulWidget {
Categories(this.stream);
final Stream<String> stream;
#override
_CategoriesState createState() => _CategoriesState();
}
class _CategoriesState extends State<Categories> {
Configuracao? conf;
List<dynamic> data = [];
List<dynamic> marcas = [];
List<dynamic> grupos = [];
var icon = Icons.ac_unit;
late String? type;
String? memo;
bool loading = true;
void _getData() async {
setState(() {
loading = true;
});
await ConfiguracaoController.fetchConfiguracao().then((value) {
setState(() {
conf = value;
});
});
type = conf!.tipoMenuHome;
grupos = await GrupoMarcaController.fetchGrupo();
marcas = await GrupoMarcaController.fetchMarca();
if (type == 'M') {
setState(() {
data = marcas;
loading = false;
});
} else {
setState(() {
data = grupos;
loading = false;
});
}
}
#override
void initState() {
super.initState();
loading = true;
_getData();
widget.stream.listen((index) {
memo = index;
if (index == 'M') {
setState(() {
data = marcas;
});
} else {
setState(() {
data = grupos;
});
}
});
}
#override
Widget build(BuildContext context) {
return data.isEmpty && !loading
? Center(
child: Icon(Icons.warning),
)
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
margin: EdgeInsets.only(top: 20.0),
height: 100.0,
child: Row(
children: [
Align(
alignment: Alignment.topCenter,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
physics: BouncingScrollPhysics(),
itemCount: loading == true ? 20 : data.length,
itemBuilder: (context, index) {
return loading == true
? Container(
height: 100,
width: 100,
margin:
EdgeInsets.only(left: 5, right: 5),
child: LoaderShimmers())
: clickable(context, index, data[index]);
})),
Container(width: 10),
],
)));
}
Container clickable(BuildContext context, int index, dynamic data) {
return Container(
margin: EdgeInsets.only(left: 5, right: 5),
width: 100.0,
child: Material(
child: Ink(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: AppStyles.darkBlue,
),
child: InkWell(
onTap: () {
kIsWeb
? Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CategoriesListWeb(data)))
: Navigator.of(context).push(MaterialPageRoute(
builder: (context) => BrandGroupList(data)));
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.only(left: 10, right: 10),
child: Text(data.nome,
maxLines: 3,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold)))
])))));
}
}
I dont know if this will work in your case, but I had a similar problem and it solved it (Scroll worked in andorid emulator and not in Chrome).
At main.dart
after
return MaterialApp.router(
add
scrollBehavior: MyCustomScrollBehavior(),
so it looks like this
#override
Widget build(BuildContext context) {
return MaterialApp.router(
scrollBehavior: MyCustomScrollBehavior(),
And at the end of the page add
class MyCustomScrollBehavior extends MaterialScrollBehavior {
// Override behavior methods and getters like dragDevices
#override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
// etc.
};
}

I want to refresh a ListView when search item is selected from dropdown

I have a screen as soon as i navigate to this screen from previous screen i fetchs data from server according to selected location. For this i call getData() method in initState() method.
On this Screen i have a searchbar on top which shows dropdown list of locations, and below i have a listview which shows data according to choosen location.
Now I want to refresh listView when a user selects any location from dropdown list from search bar. I'm stuck how can i call my getData() method again when selected loation changed.
.
class SearchResultPage extends StatefulWidget {
final TypeAheadModel selectedLocation;
SearchResultPage(this.selectedLocation);
#override
_SearchResultPageState createState() => _SearchResultPageState();
}
class _SearchResultPageState extends State<SearchResultPage>
with AutomaticKeepAliveClientMixin<SearchResultPage> {
late ScrollController _scrollController;
late FoodSearchViewModel viewModel;
#override
void initState() {
super.initState();
_scrollController = ScrollController()..addListener(_scrollListener);
viewModel = FoodSearchViewModel();
getData(widget.selectedLocation);
}
#override
void didUpdateWidget(SearchResultPage oldWidget) {
super.didUpdateWidget(oldWidget);
}
Future<List<DishModel>> getData(TypeAheadModel typeAheadModel) async {
if (!viewModel.isLoading) {
setState(() {
viewModel.isLoading = true;
});
var result = await viewModel.getData(widget.selectedLocation);
setState(() {
if (result.isEmpty || result.length == viewModel.totalCount) {
viewModel.hasLoadMore = false;
}
viewModel.isLoading = false;
viewModel.showShimmer = false;
});
}
return viewModel.foodList;
}
void _scrollListener() {
loadMore();
}
Future<List<DishModel>> loadMore() async {
if (!viewModel.isLoading) {
if (_scrollController.position.extentAfter < 150) {
setState(() {
viewModel.isLoading = true;
});
var moreItems = await viewModel.loadMore(widget.selectedLocation);
setState(() {
if (moreItems.isEmpty ||
viewModel.foodList.length == viewModel.totalCount) {
viewModel.hasLoadMore = false;
}
viewModel.isLoading = false;
});
}
return viewModel.foodList;
}
return viewModel.foodList;
}
final brandId = 0;
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (Navigator.of(context).userGestureInProgress)
return false;
else
return true;
},
child: Scaffold(
backgroundColor: kBackgroundColor,
body: Container(
child: ListView.builder(
itemCount: viewModel.showShimmer
? 5
: (viewModel.hasLoadMore
? viewModel.foodList.length + 1
: viewModel.foodList.length),
controller: _scrollController,
cacheExtent: 9999,
itemBuilder: (context, index) {
print(index);
if (!viewModel.showShimmer &&
viewModel.hasLoadMore &&
index >= viewModel.foodList.length) {
return Padding(
padding: EdgeInsets.all(5),
child: const Center(child: CupertinoActivityIndicator()),
);
} else {
if (viewModel.showShimmer) {
return buildShimmer();
} else {
return DishTile(
dishNameTextStyle: titleTextStyle,
textStyle: subtitleTextStyle,
dish: viewModel.foodList[index]);
}
}
},
),
),
),
);
}
Widget buildShimmer() => Container(
margin: EdgeInsets.symmetric(vertical: 5, horizontal: 10),
width: double.infinity,
height: 140,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
),
child: Row(
children: [
ShimmerWidget.rectangular(
height: double.infinity,
width: MediaQuery.of(context).size.width * .45,
),
Flexible(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.only(top: 8, left: 8, right: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ShimmerWidget.rectangular(height: 10),
SizedBox(
height: 10,
),
ShimmerWidget.rectangular(
height: 8,
width: MediaQuery.of(context).size.width * .30,
),
SizedBox(
height: 6,
),
ShimmerWidget.rectangular(
height: 8,
width: MediaQuery.of(context).size.width * .20,
),
],
),
),
),
)
],
),
);
#override
bool get wantKeepAlive => true;
}
It's quite hard to give any concrete example since your code is not executable but the basic idea is: use didUpdateWidget:
#override
void didUpdateWidget(SearchResultPage oldWidget) {
super.didUpdateWidget(oldWidget);
getData(widget.selectedLocation);
}
I see you already had didUpdateWidget so maybe you had an error trying to do this? If so please share the error.

click on raised button failed without any error

I have a very Strange error in my app. When i try to click on the button "PARIER" it does absolutely Nothing and i Don't see why. I called Prono_Match() but it doesn't work at all, i have no syntax errors and it does Nothing. I have tried called an other page but it is same. Why it ignores totally the call of Prono_Match ??? It is very very Strange :(
My code :
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'menu_member.dart';
import 'globals.dart' as globals;
import 'appbar_draw.dart';
import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:intl/intl.dart';
import 'prono_match.dart';
// Create a Form widget.
class Affiche_Matchs extends StatefulWidget {
#override
_Affiche_Matchs_State createState() {
return _Affiche_Matchs_State();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class _Affiche_Matchs_State extends State<Affiche_Matchs> {
#override
bool load=false;
bool visible=false;
String idmatch="";
String prono="";
List<String> radioValues = [];
Future<List<Match>> grid;
Future <List<Match>> Liste_Match_Display() async {
// SERVER LOGIN API URL
var url = 'https://www.easytrafic.fr/game_app/display_matchs.php';
var data = {
'id_membre': globals.id_membre,
};
var data_encode = jsonEncode(data);
// Starting Web API Call.
var response = await http.post(url,body: data_encode,headers: {'content-type': 'application/json','accept': 'application/json','authorization': globals.token});
// Getting Server response into variable.
var jsondata = json.decode(response.body);
List<Match> Matchs = [];
var i=0;
for (var u in jsondata) {
i=i+1;
Match match = Match(u["id"],u["equipe1"],u["equipe2"],u["dated"],u["heured"]);
Matchs.add(match);
radioValues.add("");
}
return Matchs;
}
void initState() {
super.initState();
grid = Liste_Match_Display();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[
Colors.blue[300],Colors.blue[400]
],
),
),
),
Scaffold(
appBar: drawappbar(true),
backgroundColor: Colors.transparent,
drawer: new DrawerOnly(className: Affiche_Matchs()),
body:
Center(
child : Column(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height*0.8,
width: MediaQuery.of(context).size.width,
child:
FutureBuilder(
future: grid,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Center(child: new CircularProgressIndicator(),);
default:
if(snapshot.hasError) {
return new Center(child: new Text('Error: ${snapshot.error}'),);
}
else {
List<Match> values = snapshot.data;
if (values.isEmpty) {
return Container(
child: Center(
child: Text("Aucun match disponible !!!",style: TextStyle(color: Colors.white))
)
);
}
else {
Match lastitem;
lastitem=values[0];
int i=0;
return ListView.builder(itemCount: values.length,itemBuilder: (_,index) {
bool header = lastitem.date_debut !=
values[index].date_debut;
lastitem = values[index];
var parsedDate = DateTime.parse(values[index].date_debut);
final formatter = new DateFormat('dd/MM/yyyy');
var dat = formatter.format(parsedDate);
return Column(
children: [
(header || index == 0)
?
Container(
height: 30,
margin: EdgeInsets.only(top:10),
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
border: Border.all(
color: Colors.blue[700],
width: 2,
),
color: Colors.blue[700]
),
child : new Text(dat,textAlign: TextAlign.center,style: TextStyle(fontSize: 18.0,fontWeight: FontWeight.w500,color: Colors.white),),
)// here// display header
:
Container(),
Container(
margin: EdgeInsets.only(top:20,bottom:20),
child: Center(
child: Text(values[index].heure_debut,style: TextStyle(color: Colors.white)),
),
),
Container(
margin: const EdgeInsets.only(bottom:10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
child: Text(values[index].equipe1+" - "+values[index].equipe2,style: TextStyle(fontSize:10,color: Colors.white)),
),
]
),
),
Container(
margin: const EdgeInsets.only(left:5),
child: RaisedButton(
color: Colors.green,
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(5, 5, 5, 5),
child: Text('PARIER'),
onPressed: () {
idmatch=values[index].id;
print(idmatch);
setState(() {
visible=true;
});
Prono_Match(idmatch: idmatch);
},
),
),
]
);
}
);
}
};
};
}
),
),
]
)
)
)
]
);
}
}
class Match {
final String id;
final String equipe1;
final String equipe2;
final String date_debut;
final String heure_debut;
const Match(this.id,this.equipe1, this.equipe2,this.date_debut,this.heure_debut);
}
I'm guessing you're trying to show Prono_Match page when pressing on the button, although that's not how you call a page in flutter, instead do this :
onPressed: () {
idmatch=values[index].id;
print(idmatch);
setState(() {
visible=true;
});
Navigator.push(context,MaterialPageRoute(builder: (context)=>Prono_Match(idmatch: idmatch),),
);
},