ListView.builder inside another ListView.builder - flutter

The listView.builder that is inside of the first, when i scroll the screen delete the data.
I used .insert that adds another widget inside the list.
in the first ListView.builder the data does not dissapear.
this what i do.
this is the first listView that keeps the data.
class _TestsPage extends State<TestsPage> with TickerProviderStateMixin {
final _commentController = TextEditingController();
bool _isWriting = false;
final List<CommentaryBox> _commentariBox = [];
#override
Widget build(BuildContext context) {
final model = Provider.of<Model>(context);
return DraggableScrollableSheet(
expand: false,
maxChildSize: 0.8,
initialChildSize: 0.6,
minChildSize: 0.6,
builder: (BuildContext context, ScrollController controller) => Column(
children: [
Expanded(
child: ListView.builder(
controller: controller,
physics: const BouncingScrollPhysics(),
itemBuilder: (_, i) => _commentariBox[i],
itemCount: _commentariBox.length,
//
reverse: false,
),
),
second listView.builder that delete data.
Visibility(
visible: _showComments,
child: ExpansionTile(
// initiallyExpanded: true,
title: _deployText
? Text('see less commentaries')
: Text('see commentaries'),
onExpansionChanged: (bool expanded) {
setState(
() {
_deployText = expanded;
},
);
},
children: [
ListView.builder(
physics: BouncingScrollPhysics(),
shrinkWrap: true,
itemBuilder: (_, i) => responseBox[i],
itemCount: responseBox.length,
reverse: true,
),
],
),
),
the way how I insert data to the list is the same for both
_handleResponse(String reply) {
final model = Provider.of<Model>(context, listen: false);
if (reply.isEmpty) return;
respController.clear();
final newAnswer = ResponseWidget(
reply: reply,
animationController: AnimationController(
vsync: this,
duration: Duration(milliseconds: 400),
),
);
responseBox.insert(0, newAnswer);
newAnswer.animationController.forward();
setState(() {
model.showComments= true;
});
}
}

I found the solution!
I just had to add this in my appState this: AutomaticKeepAliveClientMixin
in the constructor this:
#override
Widget build(BuildContext context) {
super.build(context);
and add the implemetation:
#override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;

Related

Flutter Listview.Builder inside bottom sheet widget not loading data on load

The below code does not display any data when the bottomsheet loads. Once the bottomsheet is loaded if I do a save operation on the code editor it loads the data. What am I missing here?
I have a bottomsheet widget which is invoked using a button.
_showBottomSheet() {
showModalBottomSheet(
context: context,
builder: (context) {
return const Contacts();
},
);
}
The above code loads up the Contacts widget that has a Listview.builder in it which is below.
class Contacts extends StatefulWidget {
const Contacts({Key? key}) : super(key: key);
#override
_ContactsState createState() => _ContactsState();
}
class _ContactsState extends State<Contacts> {
List<PhoneBookContact> phoneBookContacts1 = [];
List<PhoneBookContact> phoneBookContacts2 = [];
#override
void initState() {
loadContacts();
super.initState();
}
Future loadContacts() async {
///somecode to gather data for the listview builder
///populates the phoneBookContacts1 & phoneBookContacts2 lists
}
#override
Widget build(BuildContext context) {
return Column(children: [
const Text('Contacts Set 1'),
displayPhoneBookContacts(phoneBookContacts1),
const Text('Contacts Set 2'),
displayPhoneBookContacts(phoneBookContacts2),
]);
}
Widget displayPhoneBookContacts(phoneBookContacts) {
return Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: phoneBookContacts.length,
itemBuilder: (BuildContext context, int index) {
return Card(
margin: const EdgeInsets.all(10),
child: ListTile(
contentPadding: const EdgeInsets.all(10),
title: Column(
children: [
Text(phoneBookContacts[index].phoneBookContact.toString()),
const SizedBox(
height: 20,
),
ListView.separated(
physics: const ClampingScrollPhysics(),
shrinkWrap: true,
itemCount: phoneBookContacts[index].contactNumbers!.length,
separatorBuilder: (BuildContext context, int index) =>
const Divider(),
itemBuilder: (BuildContext context, int phoneIndex) {
return InkWell(
onTap: () {},
child: Row(
children: [
Text(phoneBookContacts[index]
.contactNumbers![phoneIndex]
.phone),
],
),
);
},
),
],
),
),
);
},
),
);
}
}
I don't prefer using FutureBuilder inside StatefulWidget., it will recall the API(future) on every setState. As for comment it is missing setState after initializing the data.
#override
void initState() {
super.initState();
loadContacts();
}
Future loadContacts() async {
///somecode to gather data for the listview builder
///populates the phoneBookContacts1 & phoneBookContacts2
if(mounted){
// if widget build then setState call.if not we don't need to call setState
// for every initstate data loading, we have to ensure it if widget is build or not. most of the case user close screen when data loading, then error happens
setState(() {});// make sure to call setState
}
}
Because function initState() don't await your loadContacts(), data loaded after function build().
You need use FutureBuilder class to rebuild ListView widget after load data
Example:
FutureBuilder(
future: loadContacts(),
builder:(context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
return Container(
child: ListView.builder(
itemCount: _faouriteList.length,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
return Text('${_faouriteList[index].title}');
}
)
);
}
}
)

flutter widget not being updated when called from a list

i previously asked a question about widgets not being updated here:
flutter slider not updating widget variables
i got a great answer which explained to me more about how states work and i experimented a little further and now have an issue where my widget inside a list is not being updated even though i update the state in a setstate.
The Widget in question not being updated is the TestBoxNumber widget in the testBoxList list after it has been added to the list. I realize that if i change the builder to return the widget itself rather than from the list it works, and i'm not sure why this is the case!
Once again any help would be greatly appreciated and i hope this helps someone facing the same issue as well :)
Main Page Code
class TestPage extends StatefulWidget {
static const id = "test_page";
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
List testBoxList = [];
List testSlideList = [];
List testParamList = [];
void updateFunc(ind, newVal) {
setState(() {
testParamList[ind] = newVal;
});
}
void addSlider() {
setState(() {
double slideValue = 0;
testParamList.add(slideValue);
int boxIndex = testParamList.length - 1;
testBoxList.add(TestBoxNumber(
numberDisplay: testParamList,
boxIndex: boxIndex,
));
testSlideList.add(TestSlider(
testValue: testParamList,
updateFunc: updateFunc,
boxIndex: boxIndex,
));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
addSlider();
},
),
body: Padding(
padding: const EdgeInsets.all(30.0),
child: ListView(
children: [
Text("Test Page"),
// Builder for viewers
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: testBoxList.length,
itemBuilder: (BuildContext ctx, int index) {
return testBoxList[index];
// return Text(testParamList[index].toString());
// return TestBoxNumber(
// numberDisplay: testParamList, boxIndex: index);
},
),
// Builder for sliders
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: testSlideList.length,
itemBuilder: (BuildContext ctx, int index) {
return testSlideList[index];
},
),
],
),
),
);
}
}
TestBoxNumber Widget
class TestBoxNumber extends StatelessWidget {
final List numberDisplay;
final int boxIndex;
TestBoxNumber({required this.numberDisplay, required this.boxIndex});
Widget build(BuildContext context) {
return Text(this.numberDisplay[this.boxIndex].toString());
}
}
Slider Widget
class TestSlider extends StatefulWidget {
List testValue;
dynamic updateFunc;
int boxIndex;
TestSlider({
required this.testValue,
required this.updateFunc,
required this.boxIndex,
});
#override
_TestSliderState createState() => _TestSliderState();
}
class _TestSliderState extends State<TestSlider> {
// double curValue = widget.testValue;
#override
Widget build(BuildContext context) {
double curValue = widget.testValue[widget.boxIndex];
return Slider(
activeColor: themeData.primaryColorLight,
value: curValue,
min: 0,
max: 100,
divisions: 50,
label: curValue.round().toString(),
onChanged: (double value) {
setState(() {
curValue = value;
});
widget.updateFunc(widget.boxIndex, value);
},
);
}
}
Me again )
Ok, so what is wrong right now is that you are using widgets, stored in the list instead of creating ones again:
You should not do this:
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: testBoxList.length,
itemBuilder: (BuildContext ctx, int index) {
return testBoxList[index];
// return Text(testParamList[index].toString());
// return TestBoxNumber(
// numberDisplay: testParamList, boxIndex: index);
},
)
but return new TestBoxNumber widgets (you actually has it commented, not sure why you did that):
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: testBoxList.length,
itemBuilder: (BuildContext ctx, int index) {
return TestBoxNumber(numberDisplay: testParamList, boxIndex: index);
},
)
so you will render widgets from scratch instead of pulling it from memory (list) and causing some weird things. Flutter is pretty optimized for such re-rendering.
So summarizing all of above: just pass data into widgets in build method. Do not store widgets in memory to reuse later.
UPD: also you can just pass double (let's call it yourDoubleValue) into TestBoxNumber instead of list and index. And then use Text('$yourDoubleValue');

Scroll Controller is not listening to scroll in Flutter

Im trying to listen to scroll for lazy loading but im not getting any Listen values,I have used ListView.builder widget and have attached a scroll contoller (_controller) and have instantiated controller in initState() method, need help with the issue
class _FeedsState extends State<Feeds> {
ScrollController _controller;
int pageNumber = 1;
#override
void initState() {
super.initState();
_controller = new ScrollController();
_controller.addListener(scrollListener);
SchedulerBinding.instance.addPostFrameCallback((_) {
_controller.animateTo(
0.0,
duration: const Duration(milliseconds: 10),
curve: Curves.easeOut,
);
});
}
#override
Widget build(BuildContext context) {
print(widget.feedDetails);
return widget.feedDetails.length == 0
? PostSomething(
isAdmin: widget.isAdmin,
)
: ListView.builder(
shrinkWrap: true,
controller: _controller,
itemBuilder: (context, index) {
return Column(
children: [
index == 0
? PostSomething(
isAdmin: widget.isAdmin,
profilePic: widget.feedDetails[0]["profile_pic"])
: Container(),
(Posts(
index: index,
feedDetails: widget.feedDetails[index],
displayProfileNavigation: widget.displayProfileNavigation,
)),
],
);
},
itemCount: widget.feedDetails.length,
);
}
void scrollListener() {
print("Scrolling");
if (_controller.position.pixels == _controller.position.maxScrollExtent) {
print("Coooool");
}
}
}
make: shrinkWrap: false,, this will enable your scrolling, if this show unbounded height exception, then try
return Scaffold(
body: Expanded(
ListView.builder(....your code..

Flutter avoid widget rebuild on collapsing/expanding ExpansionPanelList

In ExpansionPanelList, I have a problem of rebuilding widget when I expand/collapse it.
The problem is here:
expansionCallback: (int index, bool isExpanded) {
setState(() {
_profileExpansionStateMap[_profileExpansionStateMap.keys.toList()[index]] = !isExpanded;
});
},
I changed it to use Bloc state management to solve, but that has same behavior with setState(). Is there any way to avoid rebuilding widget tree? I can't use Selector widget as I don't think it would help me here.
import 'package:flutter/material.dart';
void main()=>runApp(MaterialApp(home: Home(),));
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() =>HomeState();
}
class HomeState extends State<Home> {
Map<String, bool> _profileExpansionStateMap = Map<String, bool>();
#override
void initState() {
super.initState();
_profileExpansionStateMap = {
"UserInformation": false,
"UserWeight": false,
"UserGeneralInformation": false,
};
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text('ExpansionPanel')),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(20.0),
child:ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_profileExpansionStateMap[_profileExpansionStateMap.keys.toList()[index]] = !isExpanded;
});
},
children: <ExpansionPanel>[
ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return Container(
height: 80.0,
child: Text('aaaaaaaa'),
);
},
body: Container(child:Text('aaaaaaaa')),
isExpanded: _profileExpansionStateMap["UserInformation"]),
ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return Container(
height: 80.0,
child: Text('bbbbbbbbbbbb'),
);
},
body: Container(child:Text('bbbbbbbbbbbb')),
isExpanded: _profileExpansionStateMap["UserWeight"]),
ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return Container(
height: 80.0,
child: Text('ccccccccc'),
);
},
body: Container(child:Text('ccccccccc')),
isExpanded: _profileExpansionStateMap["UserGeneralInformation"]),
],
)
),
),
);
}
}
ExpansionPanelList.expansionCallback(...) is a method that gets called whenever you tap on arrow buttons inside your ExpansionPanelList to expand/collapse it.
In this method you are actually supposed to setup your bool values passed to isExpanded of ExpansionPanel thus requiring you to call setState(...).
If you, however, have issues with this, then it clearly indicates there is something wrong with your code. So, there is no need to avoid rebuilding the widget state.

how to do pagination in GridView (Flutter)

I want to implement pagination in GridView I use GridView.builder I want to download 10 by 10 items when the user reaches the last row
You can do this using a NotificationListener. As a simple demonstration it will increase the length of your GridView whenever it reaches end of page :
var items_number = 10 ;
return NotificationListener<ScrollNotification>(
onNotification: (scrollNotification){
if(scrollNotification.metrics.pixels == scrollNotification.metrics.maxScrollExtent){
setState(() {
items_number += 10 ;
});
}
},
child: GridView.builder(
itemCount: items_number,
itemBuilder: (context, index) {
//.... the reminder of your code
}
),
);
I also needed this but couldn't find any widget for the gridview pagination, so I tried to make a component based on #Mazin Ibrahim's answer below. It seems to be working but not sure if it is the right way to do this.
typedef Future<bool> OnNextPage(int nextPage);
class GridViewPagination extends StatefulWidget {
final int itemCount;
final double childAspectRatio;
final OnNextPage onNextPage;
final Function(BuildContext context, int position) itemBuilder;
final Widget Function(BuildContext context) progressBuilder;
GridViewPagination({
this.itemCount,
this.childAspectRatio,
this.itemBuilder,
this.onNextPage,
this.progressBuilder,
});
#override
_GridViewPaginationState createState() => _GridViewPaginationState();
}
class _GridViewPaginationState extends State<GridViewPagination> {
int currentPage = 1;
bool isLoading = false;
#override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification sn) {
if (!isLoading && sn is ScrollUpdateNotification && sn.metrics.pixels == sn.metrics.maxScrollExtent) {
setState(() {
this.isLoading = true;
});
widget.onNextPage?.call(currentPage++)?.then((bool isLoaded) {
setState(() {
this.isLoading = false;
});
});
}
return true;
},
child: CustomScrollView(
slivers: <Widget>[
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 1,
mainAxisSpacing: 1,
crossAxisCount: 2,
childAspectRatio: widget.childAspectRatio,
),
delegate: SliverChildBuilderDelegate(
widget.itemBuilder,
childCount: widget.itemCount,
addAutomaticKeepAlives: true,
addRepaintBoundaries: true,
addSemanticIndexes: true,
),
),
if (isLoading)
SliverToBoxAdapter(
child: widget.progressBuilder?.call(context) ?? _defaultLoading(),
),
],
),
);
}
Widget _defaultLoading() {
return Container(
padding: EdgeInsets.all(15),
alignment: Alignment.center,
child: CircularProgressIndicator(),
);
}
}
Example -
GridViewPagination(
itemCount: 10,
childAspectRatio: 1,
itemBuilder: _buildGridItem,
onNextPage: (int nextPage) {
return fetchData();
},
)
create a Scroll controller
ScrollController _scrollController = new ScrollController();
add a scroll event listener
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
// Bottom poistion
}
});
}
Now just need to set the controller in your GridView, ListViewand ...
GridView.builder(
controller: _scrollController,
));
You can use this plugin here: Paging. Wrap your GridView inside of it and tell me if this works!