How to make the content collapse by a button in Flutter? - flutter

I have such a column, consisting as it were of the title and the content created via List.generate. How I can create an animation of collapsing this list into a title, actually by clicking on the title itself?
Column(
children: [
GestureDetector(
onTap: () {},
child: Container(
color: Theme.of(context).sliderTheme.inactiveTrackColor,
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Center(
child: Text('${category.name}',
style: Theme.of(context).textTheme.headline6.copyWith(
fontSize: 16,
color: Theme.of(context).selectedRowColor)),
),
),
),
),
Column(
children: List.generate(category.items.length, (index) {
return Container(
decoration: BoxDecoration(
border: (index + 1) != category.items.length
? Border(
bottom: BorderSide(
color: Style.inactiveColorDark.withOpacity(1.0),
width: 0.5))
: null),
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: Color(int.parse(category.color)),
shape: BoxShape.circle),
),
SizedBox(
width: 12,
),
Text('${category.items[index].description}',
style: Theme.of(context)
.primaryTextTheme
.headline1
.copyWith(fontSize: 16)),
],
),
SwitchButton(
isActive: category.items[index].isAdded,
activeColor:
Theme.of(context).sliderTheme.activeTrackColor,
inactiveColor:
Theme.of(context).sliderTheme.inactiveTrackColor,
activeCircleColor:
Theme.of(context).sliderTheme.activeTickMarkColor,
inactiveCircleColor:
Theme.of(context).sliderTheme.inactiveTickMarkColor,
turnOn: () {
ChosenFeeling removingElement = ChosenFeeling(
id: 000,
isAdded: false,
);
// If chosen list is empty
if (chosenfeelings.isEmpty) {
chosenfeelings.add(ChosenFeeling(
isAdded: true, id: category.items[index].id));
} else {
// If user tap on switchButton 2 times
chosenfeelings.removeWhere((element) {
if (element.id != null &&
element.id == category.items[index].id) {
removingElement = element;
}
return _isNeedToRemoveWhenOn(
currentItem: category.items[index],
listItem: element,
);
});
// If list isn`t empty and chosen item isn`t in list
if (category.items[index].id != removingElement.id) {
chosenfeelings.add(ChosenFeeling(
id: category.items[index].id,
isAdded: true,
));
}
}
},
turnOff: () {
ChosenFeeling removingElement = ChosenFeeling(
id: 000,
isAdded: false,
);
if (chosenfeelings.isEmpty) {
chosenfeelings.add(ChosenFeeling(
id: category.items[index].id,
isAdded: false,
));
} else {
// If user tap on switchButton 2 times
chosenfeelings.removeWhere((element) {
if (element.id != null &&
element.id == category.items[index].id) {
removingElement = element;
}
return _isNeedToRemoveWhenOff(
currentItem: category.items[index],
listItem: element,
);
});
// If list isn`t empty and chosen item isn`t in list
if (category.items[index].id != removingElement.id) {
chosenfeelings.add(ChosenFeeling(
id: category.items[index].id,
isAdded: false,
));
}
}
},
)
],
),
),
);
}),
)
],
);

The easiest way is to use expandable package for this purpose:
https://pub.dev/packages/expandable
Create expandable controller outside of build method
final ExpandableController _expandableController = ExpandableController();
And then use it like this:
ExpandableNotifier(
controller: _expandableController,
child: Column(
children: [
Expandable(
collapsed: ExpandableButton(
child: titleWidget(),
),
expanded: Column(
children: [
ExpandableButton(
child: titleWidget(),
),
list(),
]
),
),
],
),
);
Don't forget to dispose controller after using it
#override
void dispose() {
_expandableController.dispose();
super.dispose();
}
Hope this helps. You can also create your own animation with a little research

Related

Flutter : DropdownButtonFormField2 selected item changes to initial value on screen rotation

This bounty has ended. Answers to this question are eligible for a +50 reputation bounty. Bounty grace period ends in 11 hours.
KJEjava48 is looking for a canonical answer.
In my flutter application screen I have a DropdownButtonFormField2 which list various financial years, where after fetching the financial year list I set the current financial year as the initial(default) selected value in the dropdown as code below.The Problem comes when I change the financial year to a different year in dropdown, and it will change to the newly selected financial year in dropdown, but it will reset to the current(initial/default) financial year when I rotate the screen. How to solve this issue?
class AccountSetup extends StatelessWidget {
FinancialYear selFinancialPeriod = FinancialYear();
dynamic selFinancialValue;
List<dynamic>? financialYearList;
final Company selectedCompany;
AccountSetup({Key? key, required this.selectedCompany}) : super(key: key);
#override
Widget build(BuildContext context) {
context.read<MyAccountBloc>().add(FetchFinancialYearList(selectedCompany.id!));
return BlocBuilder<MyAccountBloc, MyAccountState>(
builder: (context, state) {
if(state is FinancialYearList) {
financialYearList = state.list;
if(financialYearList != null) {
for(dynamic itemFY in financialYearList!) {
if(DateTime.now().isBetween(yMdFormat.parse(itemFY['startDate']), yMdFormat.parse(itemFY['endDate']))) {
selFinancialPeriod = FinancialYear.fromJson(itemFY);
selFinancialValue = itemFY;
break;
}
}
}
getFinancialPeriodDropDown(context);
} else if(state is AccountTabChanged) {
....
} else if(state is UpdateDropDown) {
selFinancialValue = state.selValue;
selFinancialPeriod = FinancialYear.fromJson(selFinancialValue);
getFinancialPeriodDropDown(context);
}
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Stack(
children: [
Container(
child: Column(
children: [
SizedBox(height: 3,),
getFinancialPeriodDropDown(context),
SizedBox(height: 3,),
DefaultTabController(
length: 2, // length of tabs
initialIndex: 0,
child: Builder(
builder: (context) {
tabController = DefaultTabController.of(context);
tabController?.addListener(() {
selTab = tabController?.index;
context.read<MyAccountBloc>().add(ChangeTabEvent(tabIndex: tabController?.index));
});
return Column(
children: <Widget>[
....
],
);
},
),
),
],
),
),
Positioned(
bottom: 0,
left: 30,
right: 30,
child: getTabButtons(context),
),
],
)
),
),
);
}
);
}
getFinancialPeriodDropDown(BuildContext context) {
if(financialYearList == null) {
return SizedBox();
}
return setAcademicDropDown(context);
}
setFinancialPeriodDropDown(BuildContext context) {
return SizedBox(
height: 45,
child: DropdownButtonFormField2<dynamic>(
isExpanded: true,
hint: const Text('Select Financial Year',style: TextStyle(fontSize: 14),),
value: selFinancialValue,
items: financialYearList!.map((item) => DropdownMenuItem<dynamic>(
value: item,
child: Text('${dMyFormat.format(yMdFormat.parse(item['startDate']))} :: ${dMyFormat.format(yMdFormat.parse(item['endDate']))}',
style: const TextStyle(fontSize: 14,),
),)).toList(),
validator: (value) {
if (value == null) {
return 'Please select $txtAcaPer.';
}
},
onChanged: (value) {
context.read<MyAccountBloc>().add(UpdateDropDownEvent(selValue: value));
},
onSaved: (value) {},
),
);
}
}
One more thing I need to know is, how can i set the initial(default) value to nothing (ie, like 'Select Financial Year') when I open the page instead of the current financial year??
Edit :
I saw same kind of problem on the below question also
flutter dropdownbutton won't keep answer after scrolling
If you really don't want to lose the selection on orientation change then make the AccountSetup widget as StatefulWidget.
Then your code will be as following
class AccountSetup extends StatefulWidget {
final Company selectedCompany;
AccountSetup({Key? key, required this.selectedCompany}) : super(key: key);
#override
State<AccountSetup> createState() => _AccountSetupState();
}
class _AccountSetupState extends State<AccountSetup> {
FinancialYear selFinancialPeriod = FinancialYear();
dynamic selFinancialValue;
List<dynamic>? financialYearList;
#override
Widget build(BuildContext context) {
context.read<MyAccountBloc>().add(FetchFinancialYearList(widget.selectedCompany.id!));
return BlocBuilder<MyAccountBloc, MyAccountState>(
builder: (context, state) {
if(state is FinancialYearList) {
financialYearList = state.list;
if(financialYearList != null) {
for(dynamic itemFY in financialYearList!) {
if(DateTime.now().isBetween(yMdFormat.parse(itemFY['startDate']), yMdFormat.parse(itemFY['endDate']))) {
selFinancialPeriod = FinancialYear.fromJson(itemFY);
selFinancialValue = itemFY;
break;
}
}
}
getFinancialPeriodDropDown(context);
} else if(state is AccountTabChanged) {
selTab = state.tabIndex;
//tabController!.index = selTab!;
getTabButtons(context);
} else if(state is UpdateDropDown) {
selFinancialValue = state.selValue;
selFinancialPeriod = FinancialYear.fromJson(selFinancialValue);
getFinancialPeriodDropDown(context);
}
return MaterialApp(
scrollBehavior: MyCustomScrollBehavior(),
title: ....,
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSwatch().copyWith(
primary: primaryColor,
),),
home: Scaffold(
resizeToAvoidBottomInset: false,
appBar: sizedAppBar,
body: SafeArea(
child: Stack(
children: [
Container(
child: Column(
children: [
SizedBox(height: 3,),
getFinancialPeriodDropDown(context),
SizedBox(height: 3,),
DefaultTabController(
length: 2, // length of tabs
initialIndex: 0,
child: Builder(
builder: (context) {
tabController = DefaultTabController.of(context);
tabController?.addListener(() {
selTab = tabController?.index;
context.read<MyAccountBloc>().add(ChangeTabEvent(tabIndex: tabController?.index));
});
return Column(
children: <Widget>[
Container(
child: TabBar(
controller: tabController,
labelColor: textWhite1,
unselectedLabelColor: Colors.black,
indicatorWeight: 2,
isScrollable: true,
indicatorSize: TabBarIndicatorSize.tab,
indicatorColor: selColor1,
indicatorPadding: EdgeInsets.only(left: 2.0, right: 2.0,bottom: 3.0),
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(5),color: highlightGrey10,shape: BoxShape.rectangle,
),
labelPadding: EdgeInsets.symmetric (horizontal: 1),
tabs: [
Container(
width: mainTabWidth,
height: mainTabHeight,
decoration: BoxDecoration(color: tab1Color,border: Border.all(color: border1Color,width: 2,style: BorderStyle.solid),borderRadius: BorderRadius.circular(5)),
child: Tab(child:Text(tabAccSt1,textAlign: TextAlign.center,style: TextStyle(fontSize: fontSize,fontWeight: FontWeight.bold,),),),
),
Container(
width: mainTabWidth,
height: mainTabHeight,
decoration: BoxDecoration(color: tab2Color,border: Border.all(color: border1Color,width: 2,style: BorderStyle.solid),borderRadius: BorderRadius.circular(5)),
child: Tab(child:Text(tabAccSt2,textAlign: TextAlign.center,style: TextStyle(fontSize: fontSize,fontWeight: FontWeight.bold,),),),
),
],
),
),
Container(
height: 400, //height of TabBarView
decoration: BoxDecoration(
border: Border(top: BorderSide(color: Colors.grey, width: 0.5))
),
child: TabBarView(
controller: tabController,
children: <Widget>[
Container(
child: Column(
children: [
getCompanySites(),
],
),
),
Container(
child: Center(
child: Text('Display Tab 2', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
),
),
],
),
),
],
);
},
),
),
],
),
),
Positioned(
bottom: 0,
left: 30,
right: 30,
child: getTabButtons(context),
),
],
)
),
),
);
}
);
}
getFinancialPeriodDropDown(BuildContext context) {
if(financialYearList == null) {
return SizedBox();
}
return setAcademicDropDown(context);
}
setFinancialPeriodDropDown(BuildContext context) {
return SizedBox(
height: 45,
child: DropdownButtonFormField2<dynamic>(
isExpanded: true,
hint: const Text('Select Financial Year',style: TextStyle(fontSize: 14),),
value: selFinancialValue,
icon: const Icon(Icons.arrow_drop_down,color: Colors.black45,),
iconSize: 30,
buttonHeight: 60,
buttonPadding: const EdgeInsets.only(left: 20, right: 10),
dropdownDecoration: BoxDecoration(borderRadius: BorderRadius.circular(15),),
items: financialYearList!.map((item) => DropdownMenuItem<dynamic>(
value: item,
child: Text('${dMyFormat.format(yMdFormat.parse(item['startDate']))} :: ${dMyFormat.format(yMdFormat.parse(item['endDate']))}',
style: const TextStyle(fontSize: 14,),
),)).toList(),
validator: (value) {
if (value == null) {
return 'Please select $txtAcaPer.';
}
},
onChanged: (value) {
context.read<MyAccountBloc>().add(UpdateDropDownEvent(selValue: value));
},
onSaved: (value) {},
),
);
}
}
If you not seeing any changes on UI then, you have to call setState method to refresh the UI.

when we click on dropdown than we are not able to scroll the screen and text field is hide behind of keyboard in flutter

as you can see in the image I want to scroll the screen but cannot do this when the keyboard is open.
like our search result/ list is hidden behind the keyboard.
for this, I am using SingleChildScrollView, and it's working when the keyboard or dropdown is not active. when we click on dropdown or edit in the search field then it's not working.
i want to scroll the screen even keyboard is open and search field is active.
ListTileTheme(
dense: true,
contentPadding: EdgeInsets.only(left: 25, right: 25),
child: Form(
key: _formKey,
child: Padding(
padding:
EdgeInsets.only(left: 25, right: 25, top: 10),
child: Column(
children: [
Padding(
padding:
EdgeInsets.only(top: 0, bottom: 20),
child: Row(
children: [
Text(
"CONTACT",
style: TextStyle(
color: AppColor.softBlue,
fontSize: 13,
fontFamily: FontFamily
.montserratSemiBold),
),
Spacer()
],
),
),
Column(
children: [
ProfileWrapDataWithTitle(
title: "City",
),
SearchDropdownInputs(
maxHeight: 250,
modes: Mode.MENU,
dropdownHintText: widget
.salesFreshProfileData?.city
.toString() ??
"",
dropdownItem:
_selectCityData!.map((city) {
return city.cityName.toString();
}).toList(),
dropdownOnChanged: (city) {
setState(() {
_city = city.toString();
updateCity(city_name: _city);
});
},
dropdownSelectedItem: _city,
dropdownItemSuggestion:
(context, item, isSelected) {
return DropdownSuggestionItem(
dropdownPopup: item.toString(),
);
},
),
],
),
SizedBox(
height: 20,
),
Column(
children: [
ProfileWrapDataWithTitle(
title: "Project",
),
SearchDropdownInputs(
maxHeight: 250,
modes: Mode.MENU,
dropdownHintText: procheck.toString(),
dropdownItem:
_selectProjectData!.map((project) {
return project.projectName.toString();
}).toList(),
dropdownOnChanged: (pro) {
setState(() {
project = pro.toString();
print(
"this is updated project{$project}");
updatepPro(project_name: project);
});
},
dropdownSelectedItem: project,
dropdownItemSuggestion:
(context, item, isSelected) {
return DropdownSuggestionItem(
dropdownPopup: item.toString(),
);
},
),
],
),
SizedBox(
height: 20,
),
Column(
children: [
ProfileWrapDataWithTitle(
title: "Lead Stage",
),
SearchDropdownInputs(
modes: Mode.MENU,
maxHeight: 180,
dropdownHintText: "Select Lead Stage",
dropdownItem: _leadStatusConditionData!
.map((leadStatus) {
return leadStatus.statusName
.toString();
}).toList(),
dropdownOnChanged: (leadStatus) {
setState(() {
_selectLeadStatus =
leadStatus.toString();
if (_leadStatusConditionData !=
null) {
for (int index = 0;
index <
_leadStatusConditionData!
.length;
index++) {
LeadStatusCondition
leadConditionObject =
_leadStatusConditionData![
index];
if (_selectLeadStatus ==
leadConditionObject
.statusName) {
leadConditionId =
leadConditionObject
.statusId
.toString();
}
// reason(leadStatus);
}
}
_leadStatusReasonData?.length = 0;
reason(leadStatus);
});
},
// data in this
dropdownSelectedItem: leadStatus =
_selectLeadStatus,
dropdownItemSuggestion: (
context,
item,
isSelected,
) {
return DropdownSuggestionItem(
dropdownPopup: item.toString(),
);
},
),
],
),
// ]
],
),
),
),
),

How to update state within a custom tab bar with flutter

I'm trying to create a custom TabBar which navigates only forward with a button click.
im using an enum to manage the state and couple of class objects to manage items for Tabs.
What result I expected
VS
What result I've got
The code I worked out is as follows. I am still trying to figure out a way of managing the expected outcome with my code implementation. if anyone can help out will be amazing.
Object classes
class CategoryModel {
final String? title;
final eCategory category;
// final eCategoryState? catState;
CategoryModel({this.title, required this.category});
}
class CategoryStateModel {
final CategoryModel? tItem;
late eTabState? tState;
CategoryStateModel({this.tItem, this.tState});
}
The code portion
class _ContributeTabScreenState extends State<ContributeTabScreen>
with SingleTickerProviderStateMixin {
eButtonState _submitBtnState = eButtonState.bActive;
eButtonState _continueBtnState = eButtonState.bActive;
int pageNo = 0;
TabController? _tabController;
late List<eTabState>? _tabState = [];
late List<CategoryModel>? _categoryModelList = [];
late List<Tab>? _tabList = [];
late List<CategoryStateModel>? _catStateList = [];
#override
void initState() {
_categoryModelList = widget.catList;
_assignTabs();
_tabController = new TabController(
vsync: this,
length: _categoryModelList!.length, //3,
);
super.initState();
// print(_categoryModelList![0].title.toString());
}
List<Tab> _assignTabs() {
for (var item = 0; item < _categoryModelList!.length; item++) {
//
if (item != 0) {
for (var t in _categoryModelList!) {
_catStateList!.add(
new CategoryStateModel(tItem: t, tState: eTabState.tUnSelected));
}
} else {
for (var t in _categoryModelList!) {
_catStateList!.add(
new CategoryStateModel(tItem: t, tState: eTabState.tSelected));
}
}
//
_tabList!.add(
new Tab(
child: _tabItem(_catStateList![item]),
// child: _tabItem(_categoryModelList![item]),
),
);
}
return _tabList!;
}
void _goBack() {
Navigator.of(context).pop();
}
//the onPressed call back I manage the forward move of a tabbar item + tabview
void forwardTabPage() {
if (pageNo >= 0 && pageNo < _categoryModelList!.length) {
setState(() {
// });
// setState(() {
pageNo = pageNo + 1;
// _catStateList![pageNo - 1].tState = eTabState.tCompleted;
// _tabState![pageNo - 1] = _catStateList![pageNo - 1].tState!;
});
_tabController!.animateTo(pageNo);
}
}
...rest of the code
//the Tabbar item
_tabItem(CategoryStateModel item) => Container(
width: 140.0,
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
item.tItem!.title!,
style: tBody4.copyWith(color: CustomColors.mDarkBlue),
),
item.tState == _catStateList![pageNo].tState //eTabState.tCompleted
? SizedBox(
width: 8.0,
)
: SizedBox(
width: 0.0,
),
item.tState == _catStateList![pageNo].tState //eTabState.tCompleted
? CircleAvatar(
radius: 12.0,
backgroundColor: CustomColors.green600,
child: Icon(
Icons.check_outlined,
size: 20.0,
),
)
: SizedBox(
width: 0.0,
),
],
),
);
continueBtn() => ButtonWidget(
btnColor: CustomColors.green600,
borderColor: CustomColors.green600,
textColor: CustomColors.mWhite,
text: "Continue",
eButtonType: eButtonType.bText,
eButtonState: _continueBtnState,
onPressed: () {
forwardTabPage();
// _tabState = eTabState.tCompleted;
},
);
submitBtn() => ButtonWidget(
btnColor: CustomColors.green600,
borderColor: CustomColors.green600,
textColor: CustomColors.mWhite,
text: "Submit",
eButtonType: eButtonType.bText,
eButtonState: _submitBtnState,
onPressed: () {},
);
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: _categoryModelList!.length,
child: Scaffold(
appBar: AppBar(
toolbarHeight: 60.0,
leadingWidth: 100.0,
leading: GestureDetector(
onTap: _goBack,
child: Container(
child: Row(
children: [
SizedBox(
width: 16.0,
),
const Icon(
Icons.arrow_back_ios,
color: CustomColors.grey700,
size: 24.0,
),
Flexible(
child: Text(
"Back",
style: tButtonSmall,
),
),
],
),
),
),
backgroundColor: Colors.white,
elevation: 0.0,
bottom: PreferredSize(
preferredSize: Size.fromHeight(50.0),
child: IgnorePointer(
child: TabBar(
controller: _tabController,
isScrollable: false,
enableFeedback: false,
padding: EdgeInsets.only(bottom: 8.0),
indicatorSize: TabBarIndicatorSize.label,
indicator:
// _tabState == eTabState.tSelected
// ?
BoxDecoration(
borderRadius: BorderRadius.circular(40.0),
color: CustomColors.green300,
border: Border.all(color: CustomColors.mGreen, width: 2),
),
tabs: _tabList!
),
),
),
),
body: Container(
child: TabBarView(
controller: _tabController,
physics: NeverScrollableScrollPhysics(),
children: [
Container(
color: CustomColors.grey600.withOpacity(0.2),
child: Center(
child: Text("Home Tab View"),
),
),
Center(
child: Text("Map Tab View"),
),
],
),
),
persistentFooterButtons: [
Container(
width: MediaQuery.of(context).size.width,
// height: 40,
padding: EdgeInsets.symmetric(horizontal: 24.0),
margin: EdgeInsets.symmetric(vertical: 16.0),
child: (_categoryModelList!.length == 1 ||
pageNo == _categoryModelList!.length - 1)
? submitBtn()
: continueBtn(),
)
],
),
);
}
enum classes
enum eCategoryState {
cSelected,
cUnSelected,
cEnded,
}
enum eTabState {
tSelected,
tUnSelected,
tCompleted,
}
enum eCategory {
cAccess,
cAmenities,
}
I don't know if I understand what you mean or not. but try this. TabBar has a isScrollable property. set it to true

Graphql Pagination using flutter fetchmore callback calling builder method again which is refreshing my listview again from index 0

I am trying to use graphql_flutter plugin to fetch data from graphql API in this I have pagination using the offset if the offset is 0 it fetches the first 10 items if offset increases by 1 it fetches the next 10 so when I am using fetchmore callback method of graphql query it calls my builder method again which is refreshing my list again which I do not want I want once user scroll at maximum position data loads from that position only it should not reload my list and add data it just adds data to my list below is my query
my build method code
Widget build(BuildContext context) {
// TODO: implement build
MoengageFlutter.getInstance()
.trackEvent(StringConstants.loanListingScreen, null);
// Analytics().logCustomEvent(StringConstants.loanListingScreen, null);
// if (offset == 0) {
getLoanProducts =
"""query(\$offset: Int!, \$confKey: String!, \$operator: String!) {
user{
products(product_id: "",filter:[{
setting_type: "sort",
setting: [
{
conf_key: \$confKey,
operator: \$operator
},
]
}
], limit: 10, offset: \$offset) {
total
data{
id
name
loan_type
partner_id
internal_rating
playstore_rating
interest_rate_min
interest_rate_max
loan_amount_min
loan_amount_max
processing_fee_percentage
processing_fee_min
processing_fee_max
required_documents
duration_min
duration_max
duration_min_unit
duration_max_unit
app_url
partner {
id
name
logo
short_description
term_link
support_contact
support_email
}
}
}
}
}""";
// }
// productListingBloc.getProductListingBloc(getLoanProducts).then((value){
// print(value);
// });
return Query(
options: QueryOptions(document: gql(getLoanProducts), variables: {
"offset": offset,
"confKey": confKey,
"operator": _operator
}),
builder: (QueryResult result,
{VoidCallback refetch, FetchMore fetchMore}) {
if (result.hasException) {
return Container(
width: ResponsiveRenderer().widthOfItem(context),
child: UnknownErrorState(
errorImage: StringConstants.unknownErrorStateImage,
text: StringConstants.unknownErrorStateText,
titleText: StringConstants.unknownErrorOccurredText,
));
}
if (result.isLoading) {
return offset == 0
? Center(
child: CircularProgressIndicator(),
)
: Center(child: CircularProgressIndicator());
}
return Scaffold(
backgroundColor: Colors.white,
appBar: !_isAppBar
? AppBar(
automaticallyImplyLeading: false,
elevation: 0,
backgroundColor: HexColor(ColorConstants.white),
)
: AppBar(
automaticallyImplyLeading: false,
title: Container(
child: TextCustom(
text: StringConstants.loansText,
textAlign: TextAlign.left,
hexColor: ColorConstants.black,
fontSize: DimensionConstants.textSize18,
fontWeight: FontWeight.w500,
),
),
actions: [
InkWell(
onTap: () {
MoengageFlutter.getInstance().trackEvent(
StringConstants.sortButtonLoanListing, null);
Analytics().logCustomEvent(
StringConstants.sortButtonLoanListing, null);
showSortBottomSheet(refetch);
},
child: Container(
margin: EdgeInsets.only(
right: DimensionConstants.margin16,
top: DimensionConstants.margin12),
child: Row(
children: [
Container(
margin: EdgeInsets.only(
right: DimensionConstants.margin8),
child:
SvgPicture.asset("assets/loan/sort.svg"),
),
TextCustom(
text: StringConstants.sortText,
hexColor: ColorConstants.black,
fontSize: DimensionConstants.textSize14,
),
],
),
),
)
],
elevation: 0,
backgroundColor: Colors.white,
),
body: BlocBuilder<NetworkBloc, NetworkState>(
builder: (context, state) {
if (state is ConnectionFailure) {
return Center(
child: UnknownErrorState(
errorImage: StringConstants.noInternetStateImage,
titleText: StringConstants.noInternetStateText,
text: StringConstants.noInternetStateSubText,
),
);
}
ProductListing listData = ProductListing.fromJson(result.data);
if (listData.user.products == null ||
listData.user.products.data.length == 0) {
return Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Image.asset(
"assets/loan/loan_error_state.png",
width: ResponsiveRenderer().widthOfItem(context) *
0.8,
height:
ResponsiveRenderer().heightOfItem(context) *
0.4,
),
),
Container(
margin: EdgeInsets.only(
top: DimensionConstants.margin16),
child: TextCustom(
text: StringConstants.noLoansFoundText,
fontSize: DimensionConstants.textSize16,
fontWeight: FontWeight.w500,
),
),
Container(
margin: EdgeInsets.only(
top: DimensionConstants.margin16),
child: TextCustom(
text: StringConstants
.checkLocationAndPersonalDetails,
fontSize: DimensionConstants.textSize12,
hexColor: ColorConstants.greyDark,
),
),
InkWell(
onTap: () {
MoengageFlutter.getInstance().trackEvent(
StringConstants.editProfileButtonLoanListing,
null);
Analytics().logCustomEvent(
StringConstants.editProfileButtonLoanListing,
null);
Navigator.of(context)
.pushNamed(MyProfile.routeName,
arguments: MyProfile(
phoneNumber: null,
));
},
child: Container(
margin: EdgeInsets.only(
top: DimensionConstants.margin16),
child: TextCustom(
text: StringConstants.editProfileText,
fontSize: DimensionConstants.textSize14,
fontWeight: FontWeight.w500,
hexColor: ColorConstants.blue,
),
),
),
],
),
),
);
} else {
_scrollController
..addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
if (total > current) {
offset++;
FetchMoreOptions opts = FetchMoreOptions(
variables: {
'offset': offset,
"confKey": confKey,
"operator": _operator
},
document: gql(getLoanProducts),
updateQuery:
(previousResultData, fetchMoreResultData) {
print(fetchMoreResultData);
final List<dynamic> repos = [
...previousResultData['user']['products']
['data'] as List<dynamic>,
...fetchMoreResultData['user']['products']
['data'] as List<dynamic>
];
fetchMoreResultData['user']['products']['data'] =
repos;
productList = repos;
return fetchMoreResultData;
},
);
fetchMore(opts);
}
}
});
current =
(offset * limit) + listData.user.products.data.length;
total = listData.user.products.total;
return Column(
children: [
// Container(
// margin: EdgeInsets.only(
// left: DimensionConstants.margin12,
// top: DimensionConstants.margin16),
// child: TextCustom(
// text: StringConstants.loanListingDescription,
// textAlign: TextAlign.left,
// fontSize: DimensionConstants.margin14,
// hexColor: ColorConstants.black,
// ),
// ),
// _sortAndFilter(),
Expanded(
child: ListView.builder(
controller: _scrollController,
shrinkWrap: true,
itemCount: listData.user.products.data.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.only(
top: DimensionConstants.margin16,
bottom: DimensionConstants.margin16),
child: CardCustomLoanList(
listData: listData, index: index));
}),
),
],
);
}
}));
});
}```
here fetch more calling builder methods again which is refreshing my whole list I don't want to refresh my whole list
Please anyone knows any better way to do this?
[1]: https://pub.dev/packages/graphql_flutter

RangeError in PageView inside a ListView

Following my last question at
Adding elements to List
I am trying to populate a PageView with the created PostMedia items.
Here you have the complete code for my StreamBuilder:
StreamBuilder<List<Post>>(
stream: postsProvider.posts,
builder: (context, snapshot) {
if (snapshot.data != null &&
snapshot.data.isNotEmpty &&
ConnectionState.done != null) {
List<Post> listaInicial = snapshot.data;
List<Post> listaFiltrada = [];
listaFiltrada = listaInicial;
return Padding(
padding: const EdgeInsets.all(0.0),
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - 140,
child: ListView.builder(
itemCount: listaFiltrada.length,
itemBuilder: (context, indexPost) {
bool es_ambassador =
listaFiltrada[indexPost].post_autor_is_ambassador;
//ver si el post tiene media
bool tiene_media =
listaFiltrada[indexPost].post_tiene_media;
bool tiene_fotos =
listaFiltrada[indexPost].post_tiene_fotos;
List<List<PostMedia>> lista_medios;
lista_medios = [];
if (tiene_fotos) {
//foto 1
var foto1 = listaFiltrada[indexPost].foto_1;
//incluimos foto1 en la lista
List<PostMedia> lista1 = [
PostMedia(
media_url: foto1,
es_foto: true,
es_video: false,
es_youtube: false)
];
lista_medios.add(lista1);
var foto2 = listaFiltrada[indexPost].foto_2;
if (foto2.isEmpty) {
} else {
//incluimos foto1 en la lista
List<PostMedia> lista2 = [
PostMedia(
media_url: foto2,
es_foto: true,
es_video: false,
es_youtube: false)
];
lista_medios.add(lista2);
}
var foto3 = listaFiltrada[indexPost].foto_3;
if (foto3.isEmpty) {
} else {
//incluimos foto1 en la lista
List<PostMedia> lista3 = [
PostMedia(
media_url: foto3,
es_foto: true,
es_video: false,
es_youtube: false)
];
lista_medios.add(lista3);
print("lISTA3:" +
lista_medios.length.toString());
}
var foto4 = listaFiltrada[indexPost].foto_4;
if (foto4.isEmpty) {
} else {
//incluimos foto1 en la lista
List<PostMedia> lista4 = [
PostMedia(
media_url: foto1,
es_foto: true,
es_video: false,
es_youtube: false)
];
lista_medios.add(lista4);
print("lISTA4:" +
lista_medios.length.toString());
}
var foto5 = listaFiltrada[indexPost].foto_5;
if (foto5.isEmpty) {
} else {
//incluimos foto1 en la lista
List<PostMedia> lista5 = [
PostMedia(
media_url: foto5,
es_foto: true,
es_video: false,
es_youtube: false)
];
lista_medios.add(lista5);
}
}
var texto = listaFiltrada[indexPost].post_texto;
bool es_mipost = false;
var id_autor = listaFiltrada[indexPost].post_autor_id;
if (id_autor == _miId) {
es_mipost = true;
} else {
es_mipost = false;
}
bool estadenunciado =
listaFiltrada[indexPost].post_denunciado;
Timestamp recibida =
listaFiltrada[indexPost].post_fecha;
var fecha_recibida = "....";
if (recibida == null) {
} else {
DateTime date = DateTime.parse(
recibida.toDate().toString());
fecha_recibida =
DateFormat('yyyy-MM-dd HH:mm', "en")
.format(date);
fecha_recibida =
tiempoDesdeFecha(fecha_recibida);
}
return Padding(
padding:
const EdgeInsets.only(bottom: 2.0, top: 6),
child: Card(
elevation: 10,
margin: EdgeInsets.only(left: 0, right: 0),
child: Column(children: [
Row(
children: [
Column(
children: [
Padding(
padding:
const EdgeInsets.all(8.0),
child: Container(
width: 60,
height: 60,
color: Colors.transparent,
child: Stack(children: [
CircleAvatar(
radius: 31,
backgroundColor:
Colors.black,
child: CircleAvatar(
radius: 28.0,
backgroundImage: NetworkImage(
listaFiltrada[indexPost]
.post_autor_avatar),
backgroundColor:
Colors.transparent,
),
),
//si es ambassador el autor del post
es_ambassador
? Positioned(
right: 0,
bottom: 2,
child: Container(
height: 24,
width: 24,
child: Image(
image: AssetImage(
"assets/images/home_ambassador.png"))),
)
: Container(),
]),
),
),
],
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Text(
listaFiltrada[indexPost]
.post_autor_nombre,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18),
),
Text(
fecha_recibida,
style: TextStyle(fontSize: 10),
),
Text(""),
Text(""),
],
),
Spacer(),
//si es mi post
es_mipost
? PopupMenuButton(
icon: Icon(
Icons.more_vert,
size: 36,
), //don't specify icon if you want 3 dot menu
color: AppColors.rojoMovMap,
itemBuilder: (context) => [
PopupMenuItem<int>(
value: 2,
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceEvenly,
children: [
Text(
"Borrar post",
style: TextStyle(
fontWeight:
FontWeight
.bold,
fontSize: 16,
color: Colors
.white),
),
Icon(Icons.delete,
color: Colors
.white,
size: 30),
],
),
),
],
onSelected: (int indexx) async {
showConfirmacionBorradoPost(
context,
listaFiltrada[indexPost]
.post_id);
})
: PopupMenuButton(
icon: Icon(
Icons.more_vert,
size: 36,
), //don't specify icon if you want 3 dot menu
color: AppColors.rojoMovMap,
itemBuilder: (context) => [
PopupMenuItem<int>(
value: 1,
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceEvenly,
children: [
!estadenunciado
? Text(
"denunciar",
style: TextStyle(
color: Colors
.white),
)
: Text(
"ya denunciado",
style: TextStyle(
fontSize:
14,
fontWeight:
FontWeight
.bold,
color: Colors
.white),
),
!estadenunciado
? Icon(
Icons
.local_police_outlined,
color: Colors
.white,
size: 30)
: Icon(
Icons
.local_police_outlined,
color: Colors
.white,
size: 30),
],
),
),
],
onSelected: (int indexx) async {
var postADenunciar =
listaFiltrada[indexPost]
.post_id;
PostCrud().denunciarPost(
postADenunciar);
Fluttertoast.showToast(
msg:
'has denunciado el post actual',
toastLength:
Toast.LENGTH_SHORT,
gravity:
ToastGravity.CENTER,
timeInSecForIosWeb: 2,
backgroundColor:
Colors.red,
textColor: Colors.white,
fontSize: 16.0);
DateTime now =
new DateTime.now();
DateTime date = new DateTime(
now.year,
now.month,
now.day,
now.hour,
now.minute,
now.second);
String fecha_denuncia = date
.day
.toString() +
"-" +
date.month.toString() +
"-" +
date.year.toString() +
" " +
date.hour.toString() +
":" +
date.minute.toString() +
":" +
date.second.toString();
PostCrud().crearDenunciaPost(
postADenunciar,
_miId,
fecha_denuncia);
}),
//si no es mi post
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(0.0),
child: Container(
width: MediaQuery.of(context)
.size
.width,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.all(8.0),
child: ReadMoreText(
texto,
trimLines: 1,
trimMode: TrimMode.Line,
trimCollapsedText:
"leer mas",
trimExpandedText:
'leer menos',
),
),
//si tiene media => mostrar el container, si no tiene media mostrar container en blanco
tiene_media
? Container(
color: Colors.black,
height: 200,
width: MediaQuery.of(
context)
.size
.width,
child: PageView.builder(
itemCount:
lista_medios
.length,
itemBuilder:
(BuildContext
context,
int indice) {
return GestureDetector(
onTap: () {
},
child:
Container(
margin:
const EdgeInsets.all(
0),
decoration:
BoxDecoration(
image:
DecorationImage(
image:
NetworkImage(lista_medios[indice][indexPost].media_url),
fit:
BoxFit.cover,
),
)),
);
}))
: Container(
color: Colors.red,
),
],
),
),
),
],
),
]),
),
);
}),
),
);
} else {
return Container(
height: 200,
width: 200,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset("assets/images/download.png"),
]),
);
}
})
Here you have a screenshot from the page:
The PageView is working fine, the current post has 4 images, that are shown in the pageView sliding on it on every page
In my app, the posts are ordered depending on their creation datetime, newer posts are shown on top of the list of posts and then the older posts are shown below the newest one.
At this app status, all posts are shown and ordered correctly, but the posts from #2 to the bottom of the list are getting an error at the pageview at this line:
NetworkImage(lista_medios[indice][indexPost].media_url),
the given error is RangeError (index): Invalid value: Only valid value is 0: 1
You are populating a bi-dimensional list with
List<List<PostMedia>> lista_medios;
When you just need a
List<PostMedia> lista_medios;
This is because you're already building the post, fetching media at a single indexPost and then building a List of media to display. In your example you will never have more than one element in the nested list but try to access more than one with indexPost.
Then calling NetworkImage(lista_medios[indice].media_url), should work.
Don't forget to modify the other parts of your code :
PostMedia media1 =
PostMedia(
media_url: foto1,
es_foto: true,
es_video: false,
es_youtube: false);
lista_medios.add(media1);
Your own solution is working because you're always fetching the single item of the nested list. For clarity you should not use a bi-dimensional list at all.
I think you are using indice instead indexPost by mistake. change their positions like this and I think it will work:
image:
NetworkImage(lista_medios[indexPost][indice].media_url),
I have found a potential solution. When creating the PostMedia list, it is only creating one item in the list, that means that the only way to get PostMedia items is using only the index called indice:
NetworkImage(lista_medios[indice][0].media_url)
The pageview is only showing elements from lista_medios, the indexPost index is not needed.
Your data shape looks like below for lista_medios
lista_medios = [
[PostMedia()], // lista1
[PostMedia()], // lista2
[PostMedia()], // lista3
[PostMedia()], // lista4
[PostMedia()], // lista5
];
Your lista_medios can contain between 0-5 lista in the list. While lista always has one PostMedia.
When you use:
NetworkImage(lista_medios[indice][indexPost].media_url)
you got an error because you might have more than one post and indexPost might be any value greater than 0 which again exceed the size of your lista which always has one element.
Solution to your problem
While NetworkImage(lista_medios[indice][0].media_url) would work but it's unnecessary complicated data shaped in this case.
You need to render at most 5 medias from each listaFiltrada. So you can simplify the shape like below (Remove unnecessary nested list):
// sample data shape for `lista_medios`: [PostMedia(), PostMedia(), ...]
List<PostMedia> lista_medios = [];
if (!listaFiltrada[indexPost].foto_1.isEmpty) {
lista_medios.add(PostMedia(
media_url: listaFiltrada[indexPost].foto_1,
es_foto: true,
es_video: false,
es_youtube: false
));
}
if (!listaFiltrada[indexPost].foto_2.isEmpty) {
lista_medios.add(PostMedia(
media_url: listaFiltrada[indexPost].foto_2,
es_foto: true,
es_video: false,
es_youtube: false
));
}
// and so on...
Then you can use it with PageViewBuilder like below:
return PageView.builder(
itemCount: lista_medios.length,
itemBuilder: (BuildContext context, int indice) {
return Container(
child: NetworkImage(lista_medios[indice].media_url)
);
}
);