The dropdown_search library in flutter does not refresh my user list - flutter

Hello everyone and thank you for taking the time to try to help me.
I use this library in flutter on mobile only => pub.dev dropdown_search
When I click on the dropdown, I load with an API call a list of users. I want to call them 20 by 20. The first call is done well, I have my first 20 users displayed and when I get to the bottom of my list I have a new API call that goes to display the next 20. This part also works.
My problem is that the list view in the dropdown doesn't refresh and doesn't show me my next 20 users and yet in the console I have my request that leaves with my 20 new users showing. If I close and reopen the dropdown the list is updated.
I contacted the developer who told me first to try to use this => myKey.currentState.reassemble() with my scrollController but it did not work.
He then explained that to solve my problem he could only see the use of a streambuilder. I tried it but it didn't work.
Would you have some hints to give me or something else please?
Here is a bit of what I did in code. In this example I not use StreamBuilder.
final GlobalObjectKey<DropdownSearchState<ListValueOptionList>> myKey = new GlobalObjectKey<DropdownSearchState<ListValueOptionList>>(50);
bool isListValueModify = false;
List<delegationModele.Delegation> listDelegations = [];
int indexDelegationList;
int pageCounter = 0;
ScrollController _scrollController = ScrollController();
List<ListValueOptionList> listValueOptionListModule;
List<ListValueOptionList> listValueOptionListTypeOfDelegation;
List<ListValueOptionList> listValueOptionsUserDelegation;
List userAlreadyUse = [];
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
print("Scroll event");
_loadValueOptionListSpecificPeople();
setState(() {});
myKey.currentState.reassemble();
}
});
}
#override
Widget build(BuildContext context) {
return Padding(
/// Allows resize when kerboard is open if
/// padding == MediaQuery.of(context).viewInsets
padding: padding,
child: Container(
decoration:BoxDecoration(
color: appStyleMode.primaryBackgroundColor,
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0)),
),
height: modalHeight,
child: Padding(
padding: const EdgeInsets.only(left:30, top:30.0, right: 30, bottom: 20),
child: ListView(
shrinkWrap: false,
children: [
getModificationItemListDelegationBuilder(),
],
),
),
),
);
}
SingleChildScrollView getModificationItemListDelegationBuilder(){
final appStyleMode = Provider.of<AppStyleModeNotifier>(context);
return SingleChildScrollView(
child: Column(
children: [
///Add specific people
Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.only(bottom: 10, top: 10),
child: Text(
"${getTranslated(context, "my_delegation_specific_user")} : ",
style: TextStyle(
color: specificPeopleisEmpty ? Colors.red : appStyleMode.blackWhiteColor,
fontSize: 16,
),
textAlign: TextAlign.start,
),
),
Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: delegation.userDelegationRecipients.length ?? 0,
itemBuilder: (context, index){
return specificPeople(listValueOptionsUserDelegation, index);
},
),
),
],
),
);
}
Widget specificPeople(List<ListValueOptionList> listValueOptionList, int index){
final appStyleMode = Provider.of<AppStyleModeNotifier>(context);
delegationModele.UserDelegationRecipients userDelegation = delegation.userDelegationRecipients[index];
String optionUserDescription;
if(listValueOptionList != null){
listValueOptionList.removeWhere((element) => userLoad.id.stringOf == element.idValue);
for(ListValueOptionList valueOption in listValueOptionList){
if(valueOption.idValue == userDelegation.delegationUserId){
optionUserDescription = valueOption.listValueDescription;
}
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.6,
child: DropdownSearch<ListValueOptionList>(
key: myKey,
dropdownButtonProps: DropdownButtonProps(
color: appStyleMode.blackWhiteColor
),
popupProps: PopupProps.menu(
showSelectedItems: true,
menuProps: MenuProps(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
elevation: 5.0,
backgroundColor: appStyleMode.primaryBackgroundColor,
),
textStyle: TextStyle(
color: appStyleMode.blackWhiteColor
),
scrollbarProps: ScrollbarProps(
thumbVisibility: true,
thumbColor: appStyleMode.categorySelectorSelectedBackgroundColor,
interactive: true
),
listViewProps: ListViewProps(
controller: _scrollController,
shrinkWrap: true,
),
interceptCallBacks: true,
itemBuilder: (context, list, isSelected){
return new Container(
child: ListTile(
selected: isSelected,
title: Text(
list?.listValueDescription,
style: TextStyle(
color: appStyleMode.blackWhiteColor
),
),
),
);
},
loadingBuilder: (context, loadingText){
return Align(
alignment: Alignment.topCenter,
child: CircularProgressIndicator(),
);
},
emptyBuilder: (context, text){
return Align(
alignment: Alignment.topCenter,
child: Text(
"${getTranslated(context, "my_delegation_empty_search_user")}",
style: TextStyle(
color: appStyleMode.blackWhiteColor,
),
),
);
},
showSearchBox: true,
searchDelay: Duration(seconds: 1),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
enabled: true,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: appStyleMode.categorySelectorSelectedBackgroundColor,
style: BorderStyle.solid
)
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
style: BorderStyle.solid
)
),
prefixIcon: Icon(
Icons.search_rounded,
size: 20,
),
hintText: "${getTranslated(context, "my_delegation_search_hint_text")}",
hintStyle: TextStyle(
fontSize: 13
),
),
style: TextStyle(
color: appStyleMode.blackWhiteColor,
),
),
),
onChanged: (newValue){
//OnChanges is well used
},
compareFn: (value1, value2) => value1.idValue == value2.idValue,
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none
)
),
//items: listValueOptionsUserDelegation,
asyncItems: (filter) async{
return _loadValueOptionListSpecificPeople();
},
itemAsString: (ListValueOptionList list) => list.listValueDescription,
selectedItem: new ListValueOptionList(idValue: userDelegation.delegationUserId, listValueDescription: optionUserDescription),
dropdownBuilder: (context, selectedItem){
if(optionUserDescription == null || optionUserDescription.isEmpty){
return Container();
}else if(optionUserDescription == ""){
return Text("");
}else{
selectedItem.listValueDescription = optionUserDescription;
}
return textValueDescription(selectedItem.listValueDescription, appStyleMode, TextAlign.start);
},
),
),
],
);
}else{
return Container();
}
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}

I've had a similar app. Maybe this code will help:
class PostsList extends StatefulWidget {
#override
State<PostsList> createState() => _PostsListState();
}
class _PostsListState extends State<PostsList> {
final _scrollController = ScrollController();
#override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
#override
Widget build(BuildContext context) {
return BlocBuilder<PostBloc, PostState>(
builder: (context, state) {
switch (state.status) {
case PostStatus.failure:
return const Center(child: Text('failed to fetch posts'));
case PostStatus.success:
if (state.posts.isEmpty) {
return const Center(child: Text('no posts'));
}
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return index >= state.posts.length
? const BottomLoader()
: PostListItem(post: state.posts[index]);
},
itemCount: state.hasReachedMax
? state.posts.length
: state.posts.length + 1,
controller: _scrollController,
);
default:
return const Center(child: CircularProgressIndicator());
}
},
);
}
#override
void dispose() {
_scrollController
..removeListener(_onScroll)
..dispose();
super.dispose();
}
void _onScroll() {
if (_isBottom) context.read<PostBloc>().add(PostFetched());
}
bool get _isBottom {
if (!_scrollController.hasClients) return false;
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.offset;
return currentScroll >= (maxScroll * 0.9);
}
}
I've used bloc for this but it should work as well with future builder and using snapshot. Basically user scrolls down, and if scrolled more than 90% of the list another getter is send to get more items from api. If you want to replicate the whole bloc (which I strongly recommend) take look at this documentation.

Related

BLoC emit not updating the state in UI

I have build the screen with Appbar,Tabs and TabView, the Appear has TextField, every time text changes, the control is going inside the BLoC's emit but never received at UI. Please find the code below, I appreciate your any help.
class Buildings extends StatefulWidget {
const Buildings({super.key});
#override
State<StatefulWidget> createState() => _BuildingState();
}
class _BuildingState extends State<Buildings> {
late TextEditingController _searchController;
late NearbybuildingsBloc nearbybuildingsBloc;
late AllbuildingsBloc allbuildingsBloc;
late String _searchText='';
late BuildContext allBuildingContext;
#override
void initState() {
nearbybuildingsBloc = NearbybuildingsBloc();
allbuildingsBloc = AllbuildingsBloc();
_searchController = TextEditingController();
_searchController.addListener(() {
_searchText = _searchController.text;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<NearbybuildingsBloc>(create: (context) => nearbybuildingsBloc..add(NearbyBuildings())),
BlocProvider<AllbuildingsBloc>(create: (context) => allbuildingsBloc..add(AllBuildings()))
],
child: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
backgroundColor: AppColor.bt_indigo,
title: TextField(
onChanged: (value) {
allbuildingsBloc.add(
SearchbuildingLoadingEvent(query: _searchText),
);
},
style: const TextStyle(color: Colors.white),
controller: _searchController,
decoration: InputDecoration(
suffixIcon: _searchText.isEmpty
? null
:IconButton(
icon: const Icon(Icons.clear),
color: Colors.white,
onPressed: () => _searchController.clear(),
),
isDense: true,
hintText: 'Building name',
hintStyle: const TextStyle(color: AppColor.border_color),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
width: 2,
color: AppColor.text_editor_background),
borderRadius: BorderRadius.all(
Radius.circular(10.0)),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
width: 2,
color: AppColor.text_editor_background),
borderRadius: BorderRadius.all(
Radius.circular(10.0)),
),
fillColor: AppColor.text_editor_background,),
),
actions: [
NamedIcon(
text: '',
iconData: Icons.filter_list_alt,
onTap: () {
/*showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext buildContext) {
return StatefulBuilder(
builder: (buildContext, state) {
return _openFilterCategoryDialog(
filterCategories, context, state);
});
});*/
},
),
NamedIcon(
text: '',
iconData: Icons.help,
notificationCount: 1,
onTap: () {
},
)],
bottom:
_searchText.isEmpty? TabBar(
tabs: [
Tab(text: 'Near by'),
Tab(text: 'All Buildings')
],
):null,
),
body: _searchText.isEmpty?TabBarView(
children: [
// Nearby buildings
BlocBuilder<NearbybuildingsBloc, NearbybuildingsState>(
builder: (context, state) {
if( state is NearbybuildingsLoading){
return const Center(child: CircularProgressIndicator(
color: AppColor.bt_indigo,
));
}
if(state is NearbybuildingsGpsPerm){
return row.getGPS(context);
}
if (state is NearbybuildingsLoaded) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Showing ${state.nearBuildingList.length.toString()} buildings ", style: const TextStyle(
fontSize: 13,
color: AppColor.bt_indigo,
fontFamily: 'BT Regular'),
),
Expanded(
child: ListView.builder(
itemCount: state.nearBuildingList.length,
itemBuilder: (BuildContext context, int position) {
return row.getRow(state.nearBuildingList[position], context);
}),
)
],
);
}
return const Text("Failed to load buildings");
},
),
// All buildings
BlocBuilder<AllbuildingsBloc, AllbuildingsState>(
builder: (context, state) {
if( state is AllbuildingsError){
return const Text("Failed to load buildings");
}
if (state is AllbuildingsLoaded) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Showing ${state.all.length.toString()} buildings ", style: const TextStyle(
fontSize: 13,
color: AppColor.bt_indigo,
fontFamily: 'BT Regular'),
),
Expanded(
child: ListView.builder(
itemCount: state.all.length,
itemBuilder: (BuildContext context, int position) {
return row.getRow(state.all[position], context);
}),
)
],
);
}
return const Text("Failed to load buildings");
},
),
],
):
// All buildings
BlocBuilder<AllbuildingsBloc, AllbuildingsState>(
builder: (context, state) {
if( state is SearchbuildingLoadingState){
return const Center(child: CircularProgressIndicator(
color: AppColor.bt_indigo,
));
}
if (state is SearchBuildingLoadedState) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Showing ${state.searchresults.length.toString()} buildings ", style: const TextStyle(
fontSize: 13,
color: AppColor.bt_indigo,
fontFamily: 'BT Regular'),
),
Expanded(
child: ListView.builder(
itemCount: state.searchresults.length,
itemBuilder: (BuildContext context, int position) {
return row.getRow(state.searchresults[position], context);
}),
)
],
);
}
return const Text("Failed to load buildings");
},
),
// This trailing comma makes auto-formatting nicer for build methods.
),
),
);
}
}
State
class SearchBuildingLoadedState extends AllbuildingsState{
List<Building> searchresults;
SearchBuildingLoadedState({required this.searchresults});
#override
List<Object> get props {
return [searchresults];
}
}
Event
class SearchbuildingLoadingEvent extends AllbuildingsEvent {
final String query;
SearchbuildingLoadingEvent({required this.query});
#override
List<Object> get props {
return [query];
}
}
BLoC
class AllbuildingsBloc extends Bloc<AllbuildingsEvent, AllbuildingsState> {
AllbuildingsBloc() : super(AllbuildingsInitial()) {
on<SearchbuildingLoadingEvent>(_onSearchBuilding);
}
Future<void> _onSearchBuilding(SearchbuildingLoadingEvent event, Emitter<AllbuildingsState> emit) async {
emit(SearchbuildingLoadingState());
if(event.query.isNotEmpty || event.query.isNotEmpty) {
final database = await $FloorAppDataBase
.databaseBuilder('bootcamp-instagram-project.db')
.build();
final buildingDao = database.buildingDao;
var getBuilding = await buildingDao.getSearchBuildings(
event.query);
if (getBuilding != null) {
emit(SearchBuildingLoadedState(searchresults: getBuilding));
} else {
emit(SearchbuildingNoDataState());
}
}else{
emit(SearchbuildingNoDataState());
}
}
}
Please check the tab bar view to have three bloc builders where you provided,
whether you can try with
changing the tab controller
DefaultTabController(
length: 3,
child: Scaffold(
and add 1 more tabbar here
TabBar(
tabs: [
Tab(text: 'Near by'),
Tab(text: 'All Buildings')
],
)

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.

Empty container consuming space in gridview builder when searching through a list

Code :
class MobileCourses extends StatefulWidget { const MobileCourses({ Key? key }) : super(key: key);
#override _MobileCoursesState createState() =>
_MobileCoursesState(); }
class _MobileCoursesState extends State<MobileCourses> { String searchCourse = ""; TextEditingController searchController = TextEditingController(); #override Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
searchBar(),
Expanded(
child: Container(
padding: EdgeInsets.all(15),
child : FutureBuilder(
future: Networking.getAllCourses(),
builder: (_,snapshot)
{
if(snapshot.hasData)
{
List<SpecificCourse>? specificCourse = snapshot.data as List<SpecificCourse>?;
return GridView.builder(
physics: BouncingScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 15,
crossAxisSpacing: 15
),
itemCount: specificCourse![0].courses.length ,
itemBuilder: (_,index)
{
return specificCourse[0].courses[index].course.toLowerCase().contains(searchCourse) ? MobileContainer(course: specificCourse[0].courses[index].course):Container(color: Colors.red,),
// over here i am searching for my required course
},
);
}
else
{
return CircularProgressIndicator();
}
}
),
),
),
],
),
);
}
}
Search Bar :-
Widget searchBar() {
return Padding(
padding: const EdgeInsets.only(left : 15.0,right: 15.0 , top: 10.0 ,bottom: 10.0),
child: Container(
child: TextField(
decoration: InputDecoration(
hintText: "Search...",
prefixIcon: Icon(Icons.search)
),
onChanged: (value){
setState(() {
searchCourse = value.toLowerCase();
});
},
controller: searchController,
),
),
);
}
I want to implement search function inside my gridview and i have tried to implement it in the above code and the ui is looking something like this
but when i search "m" it should return me only MBA but it is returning container too i do not want these container(colored in red) i only want MBA .......I have explicity given container red for better understanding
Container without red color
i only want to return MBA but the empty container is consuming space. Please help ! and sorry for these big images i do not know how to decrease their size
Try to below code its working for me for ListView.Builder Please try to change it Gridview hope it helps you:
Create one class with your JSON Data :
class User {
String name;
int id;
int totalLeads;
User({
this.id,
this.name,
this.totalLeads,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
id: int.parse(json['userId']),
totalLeads: int.parse(json['total']),
);
}
}
Create one class that you declare voidCallback function:
class Debouncer {
final int milliseconds;
VoidCallback action;
Timer _timer;
Debouncer({this.milliseconds});
run(VoidCallback action) {
if (null != _timer) {
_timer.cancel();
}
_timer = Timer(Duration(milliseconds: milliseconds), action);
}
}
Create Stateful Widget Class
class AllAgents extends StatefulWidget {
AllAgents() : super();
#override
AllAgentsState createState() => AllAgentsState();
}
class AllAgentsState extends State<AllAgents> {
final _debouncer = Debouncer(milliseconds: 500);
List<User> users = [];
List<User> filteredUsers = [];
static String url = 'your API url here';
static Future<List<User>> getAllLeagentsList() async {
try {
final response = await http.get(url);
if (response.statusCode == 200) {
List<User> list = parseAgents(response.body);
return list;
} else {
throw Exception('Error');
}
} catch (e) {
throw Exception(e.toString());
}
}
static List<User> parseAgents(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<User>((json) => User.fromJson(json)).toList();
}
#override
void initState() {
super.initState();
getAllLeagentsList().then((usersFromServer) {
setState(() {
users = usersFromServer;
filteredUsers = users;
});
});
}
// Declare Your Widget here
Column(children:[
//Search Bar to List of typed User
Container(
padding: EdgeInsets.only(top: 20, left: 20, right: 20),
child: TextField(
textInputAction: TextInputAction.next,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Colors.grey,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.0),
borderSide: BorderSide(
color: Colors.blue,
),
),
suffixIcon: IconButton(
icon: Icon(Icons.search),
onPressed: () {},
),
contentPadding: EdgeInsets.all(15.0),
hintText: 'Search ',
),
onChanged: (string) {
_debouncer.run(() {
setState(() {
filteredUsers = users
.where((u) => (u.name
.toLowerCase()
.contains(string.toLowerCase())))
.toList();
});
});
},
),
),
//Lists of Agents
Expanded(
child: ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
padding: EdgeInsets.only(top: 20, left: 20, right: 20),
itemCount: filteredUsers.length,
itemBuilder: (BuildContext context, int index) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(
color: Colors.grey[300],
),
),
child: Padding(
padding: EdgeInsets.all(5.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ListTile(
leading: Container(
padding: EdgeInsets.all(5.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: Colors.white,
border: Border.all(color: Colors.blue),
boxShadow: [
BoxShadow(
color: Colors.black,
blurRadius: 2.0,
spreadRadius: 1.0,
offset: Offset(
1.0,
1.0,
), // shadow direction: bottom right
)
],
),
child: Text(filteredUsers[index].name[0]),
),
title: Text(
filteredUsers[index].name,
style: TextStyle(fontSize: 16),
),
)
],
),
),
);
},),),
],),
}
here is my screen without search ->
here is my screen with search ->

GlobalKey<FormState>().currentState.save() is falling when I submit a form in Flutter

Using bloc from rxdart: ^0.24.1
I am trying to save object on mysql. The first try the object get saved succefully, the second try, with a new object, it falling on formKey.currentState.save(). I am using GlobalKey<FormState>() in order to validate the form with Stream
My code is
class DetailGamePage extends StatefulWidget {
#override
_DetailGameState createState() => _DetailGameState();
}
class _DetailGameState extends State<DetailGamePage> {
final formKey = GlobalKey<FormState>();
GameBloc gameBloc;
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (gameBloc == null) {
gameBloc = Provider.gameBloc(context);
}
}
#override
Widget build(BuildContext context) {
Game _game = ModalRoute.of(context).settings.arguments;
if (_game == null) {
_game = Game(
color: "#000000",
description: "",
env: "",
isBuyIt: false,
isOnBacklog: false);
}
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(color: Colors.black),
backgroundColor: Colors.white,
title: Text(
"Add Game",
style: TextStyle(color: Colors.black),
),
actions: [
FlatButton(
onPressed: () {
if (formKey.currentState.validate()) {
formKey.currentState.save();
Fluttertoast.showToast(msg: "Game saved");
setState(() {
gameBloc.saveOrUpdate(_game, gameBloc.name,
gameBloc.description, "listGame");
});
Navigator.pushReplacementNamed(context, "home");
}
},
child: Text(
(StringUtils.isNullOrEmpty(_game.id)) ? "Add" : "Update",
style: TextStyle(color: HexColor(_game.color), fontSize: 20),
))
],
),
body: Form(
key: formKey,
child: Stack(children: <Widget>[
_createBackground(context, _game),
_createFormGame(context, _game, gameBloc)
]),
));
}
Widget _createBackground(BuildContext context, Game game) {
final size = MediaQuery.of(context).size;
final gradientTop = Container(
height: size.height, //* 0.4,
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: <Color>[HexColor(game.color), Colors.white])),
);
final circule = Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100.0),
color: Color.fromRGBO(255, 255, 255, 0.1)),
);
return Stack(
children: <Widget>[
gradientTop,
Positioned(
child: circule,
top: 90,
left: 50,
),
Positioned(
child: circule,
top: -40,
right: -30,
),
Container(
padding: EdgeInsets.only(top: 80),
child: Column(
children: <Widget>[
SizedBox(
height: 10.0,
width: double.infinity,
),
],
),
)
],
);
}
Widget _createFormGame(BuildContext context, Game game, GameBloc gameBloc) {
final size = MediaQuery.of(context).size;
return SingleChildScrollView(
child: Column(
children: <Widget>[
SafeArea(
child: Container(
height: 80.0,
)),
Container(
width: size.width * 0.85,
padding: EdgeInsets.symmetric(vertical: 50.0),
margin: EdgeInsets.symmetric(vertical: 30.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5.0),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black26,
blurRadius: 3.0,
offset: Offset(0.0, 5.0),
spreadRadius: 3.0)
]),
child: Column(
children: <Widget>[
Text("Foto", style: TextStyle(fontSize: 20.0)),
SizedBox(
height: 50.0,
),
_createNameImput(gameBloc, game),
_createDescriptionImput(gameBloc, game),
Divider(
height: 30,
color: HexColor(game.color),
indent: 30,
endIndent: 20,
),
_createWasGameImput(gameBloc, game),
Divider(
height: 30,
color: HexColor(game.color),
indent: 30,
endIndent: 20,
),
_createToTheBacklogImput(gameBloc, game),
SizedBox(height: 60),
_createDeleteButton(gameBloc, game),
SizedBox(height: 60),
],
))
],
),
);
}
#override
void dispose() {
gameBloc?.dispose();
super.dispose();
}
Widget _createWasGameImput(GameBloc gameBloc, Game game) {
return StreamBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: SwitchListTile(
activeColor: HexColor(game.color),
title: Text("Do you have it?"),
value: game.isBuyIt,
onChanged: (bool value) {
setState(() {
game.isBuyIt = value;
});
},
secondary: IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: null,
color: HexColor(game.color),
),
));
},
);
}
Widget _createToTheBacklogImput(GameBloc gameBloc, Game game) {
return StreamBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: SwitchListTile(
activeColor: HexColor(game.color),
title: Text("To the backlog?"),
value: game.isOnBacklog,
onChanged: (bool value) {
setState(() {
game.isOnBacklog = true;
});
},
secondary: IconButton(
icon: Icon(Icons.list),
onPressed: null,
color: HexColor(game.color),
),
));
},
);
}
Widget _createNameImput(GameBloc gamebloc, Game game) {
return Column(children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: TextFormField(
textCapitalization: TextCapitalization.sentences,
initialValue: game.name,
onSaved: (value) {
gameBloc.setName(value);
},
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: "Name",
icon: Icon(
Icons.games,
color: HexColor(game.color),
)),
),
),
Divider(
height: 30,
color: HexColor(game.color),
indent: 30,
endIndent: 20,
),
]);
}
Widget _createDescriptionImput(GameBloc gameBloc, Game game) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: TextFormField(
textCapitalization: TextCapitalization.sentences,
initialValue: game.description,
onSaved: (value) {
gameBloc.setDescription(value);
},
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: "Description",
icon: Icon(
Icons.description,
color: HexColor(game.color),
)),
),
);
}
Widget _createDeleteButton(GameBloc gameBloc, Game game) {
if (StringUtils.isNotNullOrEmpty(game.id)) {
return FlatButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text("Do you wan to remove the game"),
actions: <Widget>[
FlatButton(
onPressed: () {
setState(() {
gameBloc.remove(game, "listGame");
});
Navigator.pop(context);
Navigator.pop(context);
},
child: Text("Yes")),
FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: Text("No"))
],
);
});
},
child: Text("Remove Game"));
} else {
return Container();
}
}
}
This is the bloc
class GameBloc extends Validators {
//Controller
final _allDataGames = BehaviorSubject<List<Game>>();
final _descriptionController = BehaviorSubject<String>();
final _nameController = BehaviorSubject<String>();
final _allMyListGamesByNameController = BehaviorSubject<List<Game>>();
//Services
GameService gameService = GameService();
//get Data from streams
Stream<List<Game>> get allGameData => _allDataGames.stream;
Stream<List<Game>> get allGameByNameList =>
_allMyListGamesByNameController.stream;
Stream<String> get getDescriptionStream =>
_descriptionController.stream.transform(validateDescription);
Stream<String> get getNameStream =>
_nameController.stream.transform(validName);
//Observable
Stream<bool> get validateDescriptionStream =>
Rx.combineLatest([getDescriptionStream], (description) => true);
Stream<bool> get validateNameStream =>
Rx.combineLatest([getNameStream], (name) => true);
//Set Stream
Function(String) get setDescription => _descriptionController.sink.add;
Function(String) get setName => _nameController.sink.add;
//Get Stream
//From repo
void allGames() async {
List<Game> games = await gameService.getAllDataGames();
_allDataGames.sink.add(games);
}
//From my setting
void allMyListGamesByName(String listName) async {
List<Game> games = await gameService.allMyListGamesByName(listName);
_allMyListGamesByNameController.sink.add(games);
}
void saveOrUpdate(
Game game, String name, String description, String listGame) {
game.name = name;
game.description = description;
if (StringUtils.isNullOrEmpty(game.id)) {
game.id = Uuid().v1();
gameService.add(game, listGame);
} else {
gameService.update(game);
}
}
void remove(Game game, String listGame) {
gameService.remove(game, listGame);
}
//Get Lastest stream value
String get name => _nameController.value;
String get description => _descriptionController.value;
dispose() {
_descriptionController?.close();
_allMyListGamesByNameController?.close();
_allDataGames?.close();
_nameController?.close();
}
}
The provider:
class Provider extends InheritedWidget {
static Provider _imstance;
final _gameBloc = GameBloc();
factory Provider({Key key, Widget child}) {
if (_imstance == null) {
_imstance = new Provider._internal(key: key, child: child);
}
return _imstance;
}
Provider._internal({Key key, Widget child}) : super(key: key, child: child);
static GameBloc gameBloc(BuildContext context) {
return (context.inheritFromWidgetOfExactType(Provider) as Provider)
._gameBloc;
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
}
The error is:
════════ Exception caught by gesture ═══════════════════════════════════════════
Bad state: Cannot add new events after calling close
When I evaluate formKey.currentState.save(); I got:
formKey.currentState.save()
Unhandled exception:
Bad state: Cannot add new events after calling close
#0 _BroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:249:24)
#1 Subject._add (package:rxdart/src/subjects/subject.dart:141:17)
#2 Subject.add (package:rxdart/src/subjects/subject.dart:135:5)
#3 _StreamSinkWrapper.add (package:rxdart/src/subjects/subject.dart:167:13)
I was reading about this error, it mention the error is on Bloc singleston scope or dispose method.
What is happen?
When you navigate to home with Navigator.pushReplacementNamed(context, "home"), the _DetailGamePage<State> is being disposed, calling gameBloc?.dispose. This leaves _gameBloc instantiated with all streams closed.
As you are using a Singleton Provider, when you navigate back to DetailGamePage, your save is trying to write to the closed streams.
What you need to do is move the closure of the streams farther up the widget tree so as not to close them before you are done with them, perhaps at the app level OR re-instantiate _gameBloc if the streams are closed, loading the data from the repo again.

Not able to change a value in one page with respect to the value from another page in flutter

i want to change the indexvalue (pictogramindex) of one page when we click nextbutton on another screen.I will explain briefly , I have 2 screens in my scenario the first screen contains an image and it's name , a textfield and nextbutton (i have provided a dummy data contains a list of image and it's names) the logic behind this is , when we complete the textfield box and click next button(after validate) the textfield value checks with the correctvalue which i was given in the dummy data and show it's synonym which also provided. when we click the next button we will go to another page which contains the correct answer(passed from first page) and a textfield in this the user can write about the correct answer ( validated) when click next button in this page (till this my applicationworks perfectly) i want to load the first page with it's index updated (+1) which i initialised as 0 (var pictogramindex=0). But in my case when coming back to first page the index is not updating it will automatically stores the initialised value. what i want is i want to update index on the first page when i click next button in the Second page .
my source code of first screen is shown here
class Pictogramscreen extends StatefulWidget {
final int length;
const Pictogramscreen({Key key, this.length}) : super(key: key);
#override
_PictogramscreenState createState() => _PictogramscreenState();
}
class _PictogramscreenState extends State<Pictogramscreen> {
#override
final _Key = GlobalKey<FormState>();
Color defaultcolor = Colors.blue[50];
Color trueColor = Colors.green;
Color falseColor = Colors.red;
Widget defcorrect = Text('');
var pictogramindex = 0;
TextEditingController usertitleInput = TextEditingController();
nextPictogram() {
setState(() {
pictogramindex++;
});
}
fillColor() {
setState(() {
usertitleInput.text == pictdata[pictogramindex]['pictcorrectword']
? defaultcolor = trueColor
: defaultcolor = falseColor;
});
}
correctText() {
setState(() {
usertitleInput.text == pictdata[pictogramindex]['pictcorrectword']
? defcorrect = Text(pictdata[pictogramindex]['pictsynonym'])
: defcorrect = Text(pictdata[pictogramindex]['pictcorrectword']);
});
}
reset() {
setState(() {
defaultcolor = Colors.blue[50];
defcorrect = Text('');
usertitleInput.clear();
});
}
void description(BuildContext ctx) {
Navigator.of(context).pushNamed('/user-description', arguments: {
'id': pictdata[pictogramindex]['pictid'],
'word': pictdata[pictogramindex]['pictcorrectword']
});
}
Widget build(BuildContext context) {
int length = pictdata.length;
return Scaffold(
body: pictogramindex < pictdata.length
? ListView(
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 20),
padding: EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Card(
margin: EdgeInsets.only(top: 20),
child: Image.network(
pictdata[pictogramindex]['pictimg']),
),
SizedBox(
height: 10,
),
Text(
pictdata[pictogramindex]['pictword'],
style: TextStyle(
fontSize: 25,
),
),
SizedBox(
height: 10,
),
//Card(
//color: Colors.blue,
// child: TextField(
// decoration: InputDecoration.collapsed(
// hintText: 'type here'),
//textAlign: TextAlign.center,
// onSubmitted: (value) {
// usertitleInput = value;
// print(usertitleInput);
// },
// ),
//),
Form(
key: _Key,
child: TextFormField(
controller: usertitleInput,
validator: (usertitleInput) {
if (usertitleInput.isEmpty) {
return 'Answer cannot be empty';
} else {
return null;
}
},
textAlign: TextAlign.center,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.blueAccent),
borderRadius: BorderRadius.all(
Radius.circular(15),
)),
labelText: 'Type your Answer',
filled: true,
fillColor: defaultcolor,
),
onFieldSubmitted: (value) {
usertitleInput.text = value;
fillColor();
correctText();
print(usertitleInput.text);
}),
),
SizedBox(
height: 10,
),
defcorrect,
SizedBox(
height: 10,
),
RaisedButton(
onPressed: () {
if (_Key.currentState.validate()) {
description(context);
// nextPictogram();
reset();
}
//
//if (_Key.currentState.validate() == correctText()) {
// nextPictogram;
// }
},
child: Text('Next'),
)
],
),
),
],
)
: Center(
child: Text('completed'),
));
}
}
my source code of the second screen is show here
class Userinputscreen extends StatefulWidget {
final String id;
final String word;
const Userinputscreen({Key key, this.id, this.word}) : super(key: key);
#override
_UserinputscreenState createState() => _UserinputscreenState();
}
class _UserinputscreenState extends State<Userinputscreen> {
final _Keey = GlobalKey<FormState>();
TextEditingController userdescription = TextEditingController();
var pictogramindex;
void nextpict(BuildContext context) {
Navigator.of(context).pushNamed('/main-screen');
}
// void nextpict(BuildContext context, int index) {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (ctx) => Pictogramscreen(
// index: i = 0,
// )));
// }
#override
Widget build(BuildContext context) {
final routeArgs =
ModalRoute.of(context).settings.arguments as Map<String, String>;
final correctWord = routeArgs['word'];
return MaterialApp(
home: Scaffold(
body: ListView(children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 50),
child: Center(
child: Container(
padding: EdgeInsets.all(20),
margin: EdgeInsets.only(top: 100),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
correctWord,
style: TextStyle(fontSize: 26),
),
SizedBox(
height: 10,
),
Form(
key: _Keey,
child: TextFormField(
controller: userdescription,
validator: (userdescription) {
if (userdescription.isEmpty) {
return 'Answer cannot be empty';
} else {
return null;
}
},
textAlign: TextAlign.center,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blueAccent),
borderRadius: BorderRadius.all(
Radius.circular(15),
)),
labelText: 'Type your Answer',
filled: true,
),
onFieldSubmitted: (value) {
userdescription.text = value;
print(userdescription.text);
}),
),
SizedBox(
height: 10,
),
RaisedButton(
onPressed: () {
if (_Keey.currentState.validate()) {
nextpict(context);
}
},
child: Text('Next'),
)
],
),
),
),
),
])),
);
}
}
If I get it right, you basically want to tell the initial page that it's state is updated(the index) elsewhere. You basically need to make your app "reactive".
As is said in Google Developers Tutorial:
One of the advantages of Flutter is that it uses reactive views, which you can take to the next level by also applying reactive principles to your app’s data model.
Use some sort of state management. You need to choose from and use either Bloc, InheritedWidget and InheritedModel, Provider(ScopedModel), or the like.
Check this article on flutter about state management, or this for a complete list of approaches