BLoC emit not updating the state in UI - flutter

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')
],
)

Related

flutter_bloc is not resetting its Cubit Model values when we try to push the same screen more than one time

Here is my main.dart code,
void main() {
// Bloc.observer = AppBlocObserver();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<SignInCubit>(
create: (BuildContext context) => SignInCubit(),
),
BlocProvider<SignUpCubit>(
create: (BuildContext context) => SignUpCubit(),
),
],
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return OrientationBuilder(
builder: (BuildContext context, Orientation orientation) {
//sizeConfiguration
SizeConfig().init(constraints, orientation);
return MaterialApp(
title: 'Flutter Demo',
theme: lightThemeData(context),
darkTheme: darkThemeData(context),
// themeMode: _themeMode,
scaffoldMessengerKey: Messenger.rootScaffoldMessengerKey,
navigatorKey: CustomRoutes.navigatorKey,
home: const SplashPage(),
initialRoute: SplashPage.id,
routes: <String, WidgetBuilder> {
SplashPage.id:(BuildContext context) => const SplashPage(),
SignInPage.id: (BuildContext context) => const SignInPage(),
SignUpPage.id: (BuildContext context) => const SignUpPage(),
},
);
});
}),
);
}
Here is the code on SignIn screen,
class SignInPage extends StatefulWidget {
const SignInPage({super.key});
static const String id = 'SignInPage';
#override
State<SignInPage> createState() => _SignInPageState();
}
class _SignInPageState extends State<SignInPage> {
final dynamic _formKey = GlobalKey<FormState>();
final TextEditingController _passwordController = TextEditingController();
#override
void initState() {
createFocusNodeListeners();
SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
_passwordFocus.addListener(_passwordListener);
});
super.initState();
}
#override
Widget build(BuildContext context) {
final CTTheme cTTheme = Theme.of(context).extension<CTTheme>()!;
//https://medium.com/codex/flutter-bloc-an-introduction-with-cubit-7eae1e740fd0
final SignInCubitModel state = context.watch<SignInCubit>().state;
return Scaffold(
body: CustomPaint(
painter:
LoginCustomPaint(customPaintColor: cTTheme.customPaintCircleColor1),
child: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), // common padding
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(
height: 98,
),
Text(
AppStrings.welcomeTo, // heading 3
style: cTTheme.heading3,
),
const SizedBox(
height: 16,
),
RichText(
text: TextSpan(
text: AppStrings.m,
style: cTTheme.heading1, // heading1 with pink color
children: <TextSpan>[
TextSpan(
text: AppStrings.onteFiore,
style: cTTheme.heading2, //heading2 with pink color
),
TextSpan(
text: '\n${AppStrings.c}', style: cTTheme.heading1),
TextSpan(
text: AppStrings.linical, style: cTTheme.heading2),
TextSpan(
text: ' ${AppStrings.t}', style: cTTheme.heading1),
TextSpan(
text: AppStrings.rials, style: cTTheme.heading2),
],
),
),
const SizedBox(
height: 16,
),
Text(
AppStrings.findOutClinicalTrailAndStudies,
style: cTTheme.subtitle3, // subtitle 1
),
SizedBox(
height: SizeConfig.heightMultiplier! * 19.8,
),
TextFormField(
autofocus: false,
style:cTTheme.menuItemTextStyle,
controller: _passwordController,
autovalidateMode: state.passwordValidation
? AutovalidateMode.onUserInteraction
: AutovalidateMode.disabled,
obscureText: !state.signInPasswordVisibility,
// obscuringCharacter: '*',
focusNode: _passwordFocus,
decoration: kTextFieldDecoration2(context).copyWith(
labelText: AppStrings.password,
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: state.isInvalidPassWord
? state.passWordTextFormFieldColor
: cTTheme.dividerColor!)),
prefixIcon: Padding(
padding: const EdgeInsetsDirectional.only(end: 16.0),
child: Image.asset(// constants
AppIconsPath.padLockIcon),
),
suffixIcon: InkWell(
onTap: () {
context
.read<SignInCubit>()
.changeSignINPassowrdVisibilty(
state.signInPasswordVisibility
? ChangePasswordVisibilityEnum.invisible
: ChangePasswordVisibilityEnum.visible);
},
child: state.signInPasswordVisibility
? Image.asset(AppIconsPath.eyeIcon)
: Image.asset(AppIconsPath.privacyIcon),
),
),
onTap: () => context
.read<SignInCubit>()
.changePasswordTextFormFieldColor(
color: cTTheme.dividerColor!,
isInvalidPassWord: false),
onChanged: (String? value) {
_debouncer.run(() {
checkControllerValues();
});
},
onEditingComplete: () {
if (Validators.validateSignInPassword(
_passwordController.text) !=
null) {
context
.read<SignInCubit>()
.enableValidatorForPasswordTextField();
}
},
validator: (String? value) =>
Validators.validateSignInPassword(value!),
),
errorMessage(state.isInvalidPassWord, 'Password Is Incorrect'),
const SizedBox(
height: 14,
),
Align(
alignment: Alignment.bottomRight,
child: InkWell(
onTap: () async {
final bool isConnocted = await checkConnectivity();
if (isConnocted) {
CustomRoutes.push(
screen: const ForgotPasswordPage());
return;
}
CustomRoutes.push(
screen: InternetErrorScreen(
onPressed: () {
ConnectivityManager.internetChecker()
.then((bool retry) {
if (retry) {
CustomRoutes.back();
}
});
},
),
);
},
child: Text(
AppStrings.forgotPassword,
style: cTTheme.description1,
))),
const SizedBox(
height: 48,
),
PrimaryButton(
onPressed: state.isSignInButtonDissabled
? null
: () {
FocusManager.instance.primaryFocus?.unfocus();
if (_formKey.currentState!.validate()) {
context.read<SignInCubit>().onSignInSubmit(
_emailOrPhoneController.text,
_passwordController.text);
}
},
text: AppStrings.signIn.toUpperCase(),
isLoadingVisible: state.isLoading,
),
const SizedBox(
height: 16,
),
InkWell(
onTap: () async {
final bool isConnocted = await checkConnectivity();
if (isConnocted) {
Navigator.pushNamed(context, SignUpPage.id);
// CustomRoutes.pushreplace(screen: const SignUpPage());
return;
}
CustomRoutes.push(
screen: InternetErrorScreen(onPressed: () {
ConnectivityManager.internetChecker()
.then((bool retry) {
if (retry) {
CustomRoutes.back();
}
});
}));
},
child: Center(
child: RichText(
text: TextSpan(
text: AppStrings.dontHaveAnAccount,
style: cTTheme.subtitle2,
children: <TextSpan>[
TextSpan(
text: ' ${AppStrings.signUp}',
style: cTTheme.iconButtonTextStyle),
],
),
),
),
),
const SizedBox(
height: 66,
),
Center(
child: Text(
AppStrings.byLoggingInYouAgreeToOur,
style: cTTheme.noteTextStyle1,
)),
const SizedBox(
height: 6,
),
Center(
child: InkWell(
onTap: () => CustomRoutes.push(
screen: InternetErrorScreen(onPressed: () {
ConnectivityManager.internetChecker().then((bool value) {
if (value) {
CustomRoutes.back();
}
});
})),
child: Text(
AppStrings.termsAndpolicies,
style: cTTheme.noteTextStyle2,
),
)),
const SizedBox(
height: 20,
),
],
),
),
),
),
),
));
}
I am switching(show/hide) eye icon on the password textfield to show and hide the eye image using "changeSignINPassowrdVisibilty" function from cubit file. Here is the Cubit file code,
class SignInCubit extends Cubit<SignInCubitModel> {
SignInCubit()
: super(
SignInCubitModel()); // here inside SignInCubit class, all the fields are already initialized
final CTTheme cTTheme =
Theme.of(CustomRoutes.navigatorKey.currentContext!).extension<CTTheme>()!;
void changeSignINPassowrdVisibilty(ChangePasswordVisibilityEnum newState) {
emit(state.copyWith(
signInPasswordVisibility:
newState == ChangePasswordVisibilityEnum.visible));
}
}
Here is the code from SignInCubitModel class,
class SignInCubitModel {
SignInCubitModel({
this.signInPasswordVisibility = false,
});
bool signInPasswordVisibility;
SignInCubitModel copyWith({
bool? signInPasswordVisibility,
}) {
return SignInCubitModel(
signInPasswordVisibility:
signInPasswordVisibility ?? this.signInPasswordVisibility,
);
}
}
Initially signInPasswordVisibility field value is false. When user try to switch visibility of the password , signInPasswordVisibility will change its value to true or false.
Here is my problem,
1.I am enabling signInPasswordVisibility to true on SignIn screen by clicking eye icon. Please note now signInPasswordVisibility value is true.
And
2.I am pushing to Signup screen when they click signup button from SignIn screen.
And
3.I am pushing to SignIn screen again when they click SignIn button from signup screen.
But this time signInPasswordVisibility value in SignIn screen still remains true. Its supposed to be false since I am pushing to this screen again. It has to reset the all SignIn screen state values when I push to SignIn screen. But Its not happening.
Signin screen still keeps old state values even if I push to the Signin screen multiple time.
Is there any problem with the way I implemented flutter_bloc? OR Is there any solution to reset the state values every time we push the SignIn screen?

The dropdown_search library in flutter does not refresh my user list

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.

Data not updating in DB Sqflite Flutter

The task text in edit_todo_screen is not updated. I'm using the same code as in item_tasks, where I change the status of the task to move between "Done" and "Archived" - everything works well here. I tried to change only the status in edit_todo_screen, but it does not change, although the code is identical to the code in item_tasks. Perhaps the problem is that I'm not passing the parameters correctly to edit_todo_screen. I need to be able to change the status of the task and the text of the task itself in edit_todo_screen. Attached below is a screenshot of the error that occurs when clicking the button in edit_todo_screen
Tell me, please, what could be my mistake?
cubit_db
class AppCubit extends Cubit<AppStates> {
AppCubit() : super(AppInitialState());
static AppCubit get(context) => BlocProvider.of(context);
void updateDatabase(String status, int id) async {
database!.rawUpdate(
'UPDATE tasks SET status = ? WHERE id = ?', [status, id]).then((value) {
getDataBase(database);
emit(AppUpdateDatabaseState());
});
}
void createDatabase() {
openDatabase(
'todo.db',
version: 1,
onCreate: (database, version) {
database
.execute(
'CREATE TABLE tasks (id INTEGER PRIMARY KEY, title TEXT, status TEXT)')
.then((value) => print('Table Created'))
.catchError((error) {
print('Error When Creating Table ${error.toString()}');
});
},
onOpen: (database) {
getDataBase(database);
print('database opened');
},
).then((value) {
database = value;
emit(AppCreateDatabaseState());
});
}
inserToDatabase({required String title}) async {
await database!.transaction((txn) async {
txn
.rawInsert(
'INSERT INTO tasks (title, status) VALUES ("$title","New")')
.then((value) {
getDataBase(database);
print('$value Inserted Successfully');
emit(AppInsertDatabaseState());
}).catchError((error) {
print('Error When inserting Table ${error.toString()}');
});
});
}
new_tasks_list
class NewTasksScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocConsumer<AppCubit, AppStates>(
listener: (context, state) {},
builder: (context, state) {
var tasks = AppCubit.get(context).newTasks;
return SingleChildScrollView(
child: Column(children: [
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: tasks.length,
itemBuilder: (context, index) => TaskItem(tasks: tasks[index]),
),
]),
);
},
);
tasks_item
class TaskItem extends StatelessWidget {
Map? tasks;
TaskItem({this.tasks});
#override
Widget build(BuildContext context) {
return Card(
key: Key(tasks!['title']),
shadowColor: Colors.blueGrey,
margin: const EdgeInsets.only(left: 15, right: 15, top: 8),
color: Colors.black,
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.grey.shade800, width: 0.5),
borderRadius: BorderRadius.circular(10),
),
borderOnForeground: false,
child: ListTile(
title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(
// '${state.loadedUser[index].description}',
tasks!['title'],
style: const TextStyle(
fontSize: 21.0,
// fontWeight: FontWeight.bold,
),
),
// Text(
// tasks!['status'],
// style: const TextStyle(fontSize: 21.0),
// ),
]),
trailing: IconButton(
tooltip: 'Archive Todo',
highlightColor: Colors.red,
onPressed: () {
AppCubit.get(context).updateDatabase('Archive', tasks!['id']);
},
icon: const Icon(
Icons.archive,
color: Colors.white,
),
),
leading: IconButton(
tooltip: 'Done Todo',
highlightColor: Colors.green,
onPressed: () {
AppCubit.get(context).updateDatabase('Done', tasks!['id']);
},
icon: const Icon(
Icons.check,
color: Colors.white,
),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditTodoScreen(
title: tasks!['title'],
id: tasks!['id'],
),
),
);
},
),
);
}
}
edit_todo_screen
class EditTodoScreen extends StatelessWidget {
// Map? tasks;
String title;
int id;
EditTodoScreen({Key? key, required this.title, required this.id})
: super(key: key);
final _controller = TextEditingController();
#override
Widget build(BuildContext context) {
_controller.text = title;
return BlocConsumer<AppCubit, AppStates>(
listener: (context, state) {},
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: const Text(
'Edit Todo',
style: TextStyle(fontSize: 20.0),
),
),
body: _body(context),
);
});
}
Widget _body(context) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
TextFormField(
controller: _controller,
autocorrect: true,
maxLines: 2,
decoration: const InputDecoration(hintText: 'Enter todo message'),
),
const SizedBox(
height: 10.0,
),
// ElevatedButton(
// // style:,
// onPressed: () {
// AppCubit.get(context).updateDatabase('Done', id);
// },
// child: Text(
// 'Update Data',
// style: TextStyle(color: Colors.amber.shade700),
// ),
// ),
InkWell(
onTap: () {
AppCubit.get(context).updateDatabase('Done', id);
Navigator.pop(context);
},
child: _updateBtn(context),
)
],
),
);
}
Widget _updateBtn(context) {
return Container(
width: MediaQuery.of(context).size.width,
height: 50.0,
decoration: BoxDecoration(
color: Colors.black, borderRadius: BorderRadius.circular(10.0)),
child: Center(
child: Text(
'Update Todo',
style: TextStyle(
fontSize: 17.0,
color: Colors.amber.shade700,
fontWeight: FontWeight.bold),
),
),
);
}
}
I think your problem has to do with the fact that database is not set in the second case. The code fails because you try to access a null value that then is checked with the "!" operator. Look where you set the database and check if that code is called in both code flows.
edit:
I think this line in edit todo screen is your problem: AppCubit.get(context).updateDatabase('Done', id);. If I am not mistaken AppCubit.get(context) returns null. The easiest way to check if I am right would be to replace it with the following:
final appCubit = AppCubit.get(context);
print('$appCubit');
appCubit.updateDatabase('Done', id);
If I am right, you should see "null" in your terminal.
What I think happens is, that the app cubit is not provided in the context anymore, because you pushed the todo screen as a new screen on the navigator. With that, the context that is provided is that of the navigator (or probably the material app in your case) which is above the point where you provide the AppCubit.
I am kind of guessing though, because I only see half of your code. I hope it helps nevertheless. :)

flutter riverpod leaving screen then come back it doesn't maintain the state

So I have two screens:
-Book_screen to display all the books(click on any book to go to article_screen)
-article_screen to display articles
In article_screen, I can click on article to save it as favorites.
but when I go back to book_screen then come back to article_screen, those favorited articles doesn't show the favorited status(icon red heart).
this is my article screen code:
class ArticleENPage extends ConsumerStatefulWidget{
final String bookName;
const ArticleENPage({Key? key,#PathParam() required this.bookName,}) : super(key: key);
#override
ArticleENScreen createState()=> ArticleENScreen();
}
class ArticleENScreen extends ConsumerState<ArticleENPage> {
late Future<List<Code>> codes;
#override
void initState() {
super.initState();
codes = fetchCodes();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.bookName,style: const TextStyle(fontSize: 24,fontWeight: FontWeight.bold),),backgroundColor: Colors.white,foregroundColor: Colors.black,elevation: 0,),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: Container(
margin: const EdgeInsets.only(top:10),
height: 43,
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
border: Border.all(
color: Colors.black.withOpacity(0.32),
),
),
child: Consumer(
builder: (context,ref,_) {
return TextField(
onChanged: (value) {
searchStringController controller = ref.read(searchStringProvider.notifier);
controller.setText(value.toLowerCase());
},
decoration: const InputDecoration(
border: InputBorder.none,
icon: Icon(Icons.search,size:18),
hintText: "Search Here",
hintStyle: TextStyle(color: Color.fromRGBO(128,128, 128, 1)),
),
);
}
),
),
),
const SizedBox(height: 10),
Expanded(
child: FutureBuilder(
builder: (context, AsyncSnapshot<List<Code>> snapshot) {
if (snapshot.hasData) {
return Center(
child: Consumer(
builder: (context,ref,child) {
final searchString = ref.watch(searchStringProvider);
return ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return snapshot.data![index].name
.toLowerCase()
.contains(searchString) ||
snapshot.data![index].description
.toLowerCase()
.contains(searchString)
? Consumer(
builder: (context,ref,child) {
final favlist = ref.watch(FavoriteListController.favoriteListProvider);
print(favlist);
final alreadySaved = favlist.contains(snapshot.data![index]);
return Card(
child:Padding(
padding: const EdgeInsets.all(10),
child:ExpandableNotifier(
child: ScrollOnExpand(
child: ExpandablePanel(
theme: const ExpandableThemeData(hasIcon: true),
header: RichText(text: TextSpan(children: highlight(snapshot.data![index].name, searchString,'title')),),
collapsed: RichText(text: TextSpan(children: highlight(snapshot.data![index].description, searchString,'content')), softWrap: true, maxLines: 3, overflow: TextOverflow.ellipsis,),
expanded: Column(
children: [
RichText(text: TextSpan(children: highlight(snapshot.data![index].description, searchString,'content')), softWrap: true ),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
semanticLabel: alreadySaved ? 'Remove from saved' : 'Save',
),
onPressed: () {
FavoriteListController controller = ref.read(FavoriteListController.favoriteListProvider.notifier);
if (alreadySaved) {
controller.toggle(snapshot.data![index]);
} else {
controller.toggle(snapshot.data![index]);
}
},
),
IconButton(
icon: const Icon(Icons.content_copy),
onPressed: () {
setState(() {
Clipboard.setData(ClipboardData(text: snapshot.data![index].name+"\n"+snapshot.data![index].description))
.then((value) {
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(content: Text('Copied')));
},);
});
},
),],),],)),),)));})
: Container();
},
separatorBuilder: (BuildContext context, int index) {
return snapshot.data![index].name
.toLowerCase()
.contains(searchString) ||
snapshot.data![index].description
.toLowerCase()
.contains(searchString)
? Divider()
: Container();
},
);
}
),
);
} else if (snapshot.hasError) {
return const Center(child: Text('Something went wrong :('));
}
return const Align(alignment:Alignment.topCenter,child:CircularProgressIndicator());
},
future: codes,
),
),
],
),
);
}
//read from files
Future<List<Code>> fetchCodes() async {
final response =
await rootBundle.loadString('assets/articles.json');
var CodeJson = json.decode(response)[widget.bookName] as List<dynamic>;
return CodeJson.map((code) => Code.fromJson(code)).toList();
}
}
I tried using riverpod for provider and save to sharedpreference the list of code that I favorited.
final sharedPrefs =
FutureProvider<SharedPreferences>((_) async => await SharedPreferences.getInstance());
class FavoriteListController extends StateNotifier<List<Code>>{
FavoriteListController(this.pref) : super(Code.decode(pref?.getString("favcode")??""));
static final favoriteListProvider = StateNotifierProvider<FavoriteListController, List<Code>>((ref) {
final pref = ref.watch(sharedPrefs).maybeWhen(
data: (value) => value,
orElse: () => null,
);
print(pref?.getString("favcode"));
return FavoriteListController(pref);
});
final SharedPreferences? pref;
void toggle(Code code) {
if (state.contains(code)) {
state = state.where((id) => id != code).toList();
} else {
state = [...state, code];
}
final String encodedData = Code.encode(state);
pref!.setString("favcode", encodedData);
}
}
I am not sure what is the cause of this but I think it might be because of futurebuilder? I am confused to how to solve this issue...
I am stuck in a dead end so any help or advice would be really appreciated
edit 1-
this is my source code in case I have not include all the necessary codes
https://github.com/sopheareachte/LawCode
edit-2
do I need to change "late Future<List> codes;" that fetch all the codes for futurebuilder to riverpod futureprovider too for it to work?
Maybe the problem is, that you define a static provider inside of your controller class. Try this code:
final sharedPrefs = FutureProvider<SharedPreferences>((_) async => await SharedPreferences.getInstance());
final favoriteListProvider = StateNotifierProvider<FavoriteListController, List<Code>>((ref) {
final pref = ref.watch(sharedPrefs).maybeWhen(
data: (value) => value,
orElse: () => null,
);
print(pref?.getString("favcode"));
return FavoriteListController(pref);
});
class FavoriteListController extends StateNotifier<List<Code>>{
FavoriteListController(this.pref) : super(Code.decode(pref?.getString("favcode")??""));
final SharedPreferences? pref;
void toggle(Code code) {
if (state.contains(code)) {
state = state.where((id) => id != code).toList();
} else {
state = [...state, code];
}
final String encodedData = Code.encode(state);
pref!.setString("favcode", encodedData);
}
}

Immediate invoking function inside build(context) returns Null(Flutter 2.0)

For an Alert widget which has to be conditionally rebuilt every 3 seconds, on resetting the build state (_rebuild ) using an Immediate Invoking Function.
However the Immediate Invoking Function in which the Alert widget is wrapped
returns Null.
Since build(context) only accepts Widgets,is it even possible to use Immediate Invoking Function in build(context) to alter conditional parameters?
ERROR
Column's children must not contain any null values, but a null value was found at index 1
CODE
import 'package:flutter/material.dart';
import 'dart:async';
void main () => runApp(MaterialApp(
theme: ThemeData.dark(),
home: ConditionalWidgets(),
));
class ConditionalWidgets extends StatefulWidget {
#override
_ConditionalWidgetsState createState() => _ConditionalWidgetsState();
}
class _ConditionalWidgetsState extends State<ConditionalWidgets> {
bool _reBuild = true ;
int _counter = 0;
void initState(){
super.initState();
_reBuildTimer();
}
void _reBuildTimer() {
Timer.periodic(Duration(seconds: 3), (timer) {
setState() {
_counter++;
_reBuild = true;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body : Center(child:Column(
children : [
Container(
margin: EdgeInsets.all(50),
padding: EdgeInsets.all(60),
decoration: BoxDecoration(
shape :BoxShape.rectangle,
border: Border.all(
color: Colors.orange,
width: 8,
),
borderRadius: BorderRadius.circular(10),
),
child:FittedBox(child: Text('Always Visible Widget', style: TextStyle()))),
// Conditional _reBuild Alert - returns NUll
if( _reBuild)
(){
AlertDialog(
title: Text('Conditionally Visible Widget'),
content: FittedBox(child: Text(
'Rebuild Count = $_counter', style: TextStyle())));
print('_reBuild = $_reBuild');
_reBuild = false;//Reset _reBuild using Immediate Invoking Function
}()
],
),),
);
}
}
I have something similar implemented for an app. I use streams to rebuild the widget
you can play with the code to get desired output
Alert Dialoge
showDialog(
context: context,
builder: (context) {
return StreamBuilder(
stream: bloc.eventStream,
builder: (context, snapshot) {
if (snapshot.data == Event.Success) {
return AlertDialog(
scrollable: true,
content: Column(
children: [
Icon(
Icons.check_circle,
color: Colors.green[800],
size: 300,
),
Text(
"Added Successfully !",
style: TextStyle(fontSize: 24),
)
],
),
);
} else if (snapshot.data == Event.Failed) {
return AlertDialog(
scrollable: true,
content: Column(
children: [
Icon(
Icons.cancel,
color: Colors.red[800],
size: 300,
),
Text(
"Invalid Fields",
style: TextStyle(fontSize: 24),
)
],
),
);
} else if (snapshot.data == Event.Error) {
return AlertDialog(
scrollable: true,
content: Column(
children: [
Icon(
Icons.warning_rounded,
color: Colors.yellow[800],
size: 300,
),
Text(
"Unknown Error",
style: TextStyle(fontSize: 24),
)
],
),
);
} else {
return AlertDialog(
scrollable: true,
content: Column(
children: [
SizedBox(
height: 150,
width: 150,
child: CircularProgressIndicator(strokeWidth: 5.0)),
Text(
"Loading ",
style: TextStyle(fontSize: 24),
)
],
),
);
}
});
},
);
stream controller
import 'dart:async';
enum Event{Loading,Failed,Error,Success}
class addEmpbloc{
final _eventStreamController = StreamController<Event>.broadcast();
StreamSink<Event> get eventSink => _eventStreamController.sink;
Stream<Event>get eventStream => _eventStreamController.stream;
void dispose(){
_eventStreamController.close();
}
}
void _reBuildTimer() {
Timer.periodic(Duration(seconds: 3), (timer) {
bloc.eventSink.add(Event.Success);
});
}
Instead of enum, u can use the data type of u r choice