I am not able to access TextEditingController using bloc in Flutter - flutter

How to access TextEditingController(); using bloc . here my code :-
after i use
BlocProvider.of<UsernameupdateCubit>(context).nameUpdate(userUpdateController.text);
I got an error which is
BlocProvider.of() called with a context that does not contain a UsernameupdateCubit.
Please check :
I use
final userUpdateController = TextEditingController();
then
TextFormField(
controller: userUpdateController,
decoration: InputDecoration(
labelText: "Full Name",
border: OutlineInputBorder(),
prefixIcon: Icon(FeatherIcons.user, size: 24),
),
),
after that use a button to save/sent/access anywhere or display in list view :
button is :
Container(
margin: EdgeInsets.only(top: 10),
alignment: AlignmentDirectional.centerEnd,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 1,
primary: Color(0xff34495e),
),
onPressed: () {
// BlocProvider.of<UsernameupdateCubit>(context).nameUpdate();
// BlocProvider.of<WeatherBloc>(context)
BlocProvider.of<UsernameupdateCubit>(context)
.nameUpdate(userUpdateController.text);
},
child: Text(
"Continue",
),
),
),
My Cubit :
class UsernameupdateCubit extends Cubit<UsernameupdateState> {
UsernameupdateCubit() : super(UsernameupdateState(userUpdateName: ''));
void nameUpdate(userUpdateController) =>
emit(UsernameupdateState(userUpdateName: userUpdateController.text));
}
and state is :
class UsernameupdateState {
String userUpdateName;
UsernameupdateState({
required this.userUpdateName,
});
}

all are same but I include BlocProvider in :
using multibloc:
BlocProvider(
create: (context) => UsernameupdateCubit(),
),
after that I changed my cubit :
void nameUpdate(userUpdateController) =>
emit(UsernameupdateState(userUpdateName: userUpdateController));
and yes that's it .
thank you – #nvoigt #nvoigt

Related

Good way to mange state inside dialog with a form on it, being displayed from page with its own bloc?

See for the invoice page I have BlocBuilder wrapped in a scaffold of stateful page, inside that body under several widgets is a call to future void in separate file call to create a dialog widget. And inside the dialog method is a call to create an invoice form which is in a separate file and is stateful class displayed to be displayed on the dialog screen. In this form the user will be able to add and delete UI elements from a list view what I need to do is rebuild the widget either dialog screen/form or the list view/ to reflect the changes made by the user
import 'package:flutter/material.dart';
import 'dart:developer' as dev;
import 'package:track/src/features/invoices/application/bloc.dart';
import 'package:track/src/features/invoices/application/events.dart';
import 'package:track/src/features/invoices/application/pdf_invoice_api.dart';
class InvoiceForm extends StatefulWidget {
final InvoiceBlocController blocController;
const InvoiceForm(this.blocController, {Key? key}) : super(key: key);
#override
State<InvoiceForm> createState() => _InvoiceFormState();
}
class _InvoiceFormState extends State<InvoiceForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: TextEditingController()
..text = widget.blocController.invoice.client,
validator: (value) {
value!.isEmpty ? 'Enter a value for client' : null;
},
style: Theme.of(context).textTheme.labelMedium,
decoration: InputDecoration(
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
),
),
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
),
),
labelText: 'Client:',
labelStyle: Theme.of(context).textTheme.labelMedium),
),
TextFormField(
controller: TextEditingController()
..text =
'${widget.blocController.invoice.projectNumber}-${widget.blocController.invoice.invoiceNumber}',
validator: (value) {
value!.isEmpty ? 'Enter a valid project number' : null;
},
style: Theme.of(context).textTheme.labelMedium,
decoration: InputDecoration(
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
),
),
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
),
),
labelText: 'Client:',
labelStyle: Theme.of(context).textTheme.labelMedium),
),
ListView.builder(
shrinkWrap: true,
itemCount: widget.blocController.invoice.items.length,
itemBuilder: (context, index) {
final item = widget.blocController.invoice.items[index];
return ListTile(
contentPadding: EdgeInsets.zero,
trailing: IconButton(
onPressed: () {
widget.blocController.add(DeleteItemFromInvoice(index));
},
icon: Icon(Icons.delete)),
title: Column(
children: [
Row(
children: [
itemTextFormField(
initialValue: item.name ?? '',
labelText: 'name',
index: index),
SizedBox(width: 20),
itemTextFormField(
initialValue: item.description ?? '',
labelText: 'description',
index: index),
],
),
Row(
children: [
itemTextFormField(
initialValue: item.quantity.toString(),
labelText: 'quantity',
index: index),
SizedBox(width: 20),
itemTextFormField(
initialValue: item.costBeforeVAT.toString(),
labelText: 'Cost Before VAT',
index: index),
],
),
SizedBox(height: 30),
Divider(
thickness: 2,
color: Colors.black,
)
],
),
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
onPressed: () {
dev.log('button clicked to add new item');
widget.blocController.add(AddNewItemToInvoice());
},
icon: Icon(Icons.add)),
IconButton(
onPressed: () async {
_formKey.currentState!.save();
Navigator.pop(context);
await PdfInvoiceApi.generate(widget.blocController.invoice);
},
icon: Icon(Icons.send))
],
)
],
),
);
}
Expanded itemTextFormField({
required String initialValue,
required String labelText,
required int index,
}) {
return Expanded(
child: TextFormField(
controller: TextEditingController()..text = initialValue,
onSaved: (newValue) {
widget.blocController.add(UpdateInvoiceDetails(index));
},
style: Theme.of(context).textTheme.labelMedium,
decoration: InputDecoration(
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
),
),
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
),
),
labelText: labelText,
labelStyle: Theme.of(context).textTheme.labelMedium,
),
),
);
}
}
InvoiceDialog Source code: https://pastebin.com/PCjmCWsk
InvoiceDialog Source code: https://pastebin.com/VS5CG22D
Edit 2: Made the follwoing changes to bloc per Mostafa answer as best I could, getting pressed against a deadline here so really need some help:
These changes were to main page calling the show dialog passing bloc.
showDialog(
context: context,
builder: (context) => BlocProvider.value(
value: blocController,
child: InvoiceDetailsDialog(
screenWidth: screenWidth,
screenHeight: screenHeight),
),
);
This file was the original place where showdialog was called and was custom Future return showDialog.
Results: showDialog takes enitre screen. Rendering Invoice form reulsts in error being displayed in place of the form:
No Material widget found.
Edit 3: fixed previous error but back where i started bloc is still being called succesfully but no changes to the ui:
Widget build(BuildContext context) {
final blocController = BlocProvider.of<InvoiceBlocController>(context);
return Center(
child: Material(color: Colors.red,
borderRadius: BorderRadius.circular(50),
child: SizedBox(
width: screenWidth / 2, height: screenHeight / 2,
child: Padding(padding: const EdgeInsets.all(20),
child: Column(children: [
Expanded(child: ListView(children: [
Text('Invoices',
style: Theme.of(context)
.textTheme.bodyLarge?.copyWith(color: Colors.white)),
InvoiceForm()
]))])))));
}
As form nothing changed except instead of passing the blocController through a method I am now calling it like:
class _InvoiceFormState extends State<InvoiceForm> {
final _formKey = GlobalKey<FormState>();
late final InvoiceBlocController blocController;
#override
void initState() {
blocController = BlocProvider.of<InvoiceBlocController>(context);
super.initState();
}
Still nothing changes.
Edit 4: Set state does work and leaving in bloc code was executing and if I clicked add two items would be added or delete would remove two items. But with setstate commented out it went back to not rebuilding. Using setstate for now but not preferred.
Edit 5: Don't if this is still being paid attention to hopefully is. Can I keep add add events like: add(NewItem), add(deleteItem),add(GeneratePDF). Without changing state. currently I have done that once so far. Is this bad practice
You can pass the main bloc to the dialog widget and call the bloc function that you want and it will reflect on the main screen
How can you do this? by injecting the MainBloc value to DialogWidget with BlocProvider.value
MainWidget
class MainWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => MainBloc(),
child: BlocConsumer<MainBloc, MainStates>(
listener: (BuildContext context, MainStates state) {},
builder: (BuildContext context, MainStates state) {
final bloc = MainBloc.get(context);
return GestureDetector(
onTap: () {
showDialog(
context,
builder: (context) => BlocProvider.value(
value: bloc,
child: WidgetTwoDialog(),
),
);
},
child: Item(),
);
},
),
);
}
}
DialogWidget
class DialogWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final bloc = MainBloc.get(context);
return GestureDetector(
onTap: () {
bloc.addToList();
},
child: Text('Remove form the main screen'),
);
}
}
Also, this answer might help you to get my point well here
you can wrap your dialog within the stateful builder and then you will get the method to set your dialog state.

State of the dialog not updating on dropdown change

So I have this function to show a reusable dialog that have a list of widget. The main problem lies in the dropdown and the text field. to begin with my dropdown value is A and I wanted the text field to only show when the dropdown value is B.
I tried some code already but when I choose the value B the text field won't show up, then if I close the dialog and try to open it again next time, that will show the text field that I want, but the value in the drop down is A not value B.
How can I achieve something like what I wanted ?
Here's my code :
List<String> itemList = ["notMyExpertise", "Alasan lainnya"];
String selectedReason = '';
void rejectDialog(
{required dynamic ticketData,
required String ticketId,
required VoidCallback onDeclineConfirmed}) {
showDialog(
context: context,
builder: (context) {
return ReusableConfirmationDialog(
titleText: 'hello'.tr(),
contentText: 'hello bro let me try to help you first before '.tr(),
confirmButtonText: 'sure'.tr(),
declineButtonText: 'cancel'.tr(),
onDecline: () {
Navigator.pop(context);
},
onConfirm: onDeclineConfirmed,
additionalWidget: Column(
children: [
ReusableDropdownButton(
itemList: itemList,
width: 197,
height: 26,
onChanged: (newValue) {
setState(() {
selectedReason = newValue;
});
},
),
const SizedBox(height: 10),
if (selectedReason == 'Alasan lainnya')
Container(
constraints: const BoxConstraints(minHeight: 78),
width: 230,
decoration: BoxDecoration(
color: othersChatColor,
borderRadius: BorderRadius.circular(15),
),
padding:
const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
child: TextFormField(
controller: ticketTextController,
maxLength: 100,
inputFormatters: [
LengthLimitingTextInputFormatter(1000),
],
style: primaryColor400Style.copyWith(
fontSize: fontSize11,
),
maxLines: null,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
hintText: 'reasonHint'.tr(),
hintStyle: weight400Style.copyWith(
color: hintColor, fontSize: fontSize11),
counterText: '',
),
),
),
const SizedBox(height: 5),
],
),
);
});
}
Because the dialog and bottom sheets don't have states so you must create a different stateful widget for the widget you want to show in the dialog or sheet and then return it from the dialog or bottom sheet. that's how it will work as a stateful widget and will update as you want.
void rejectDialog(
{required dynamic ticketData,
required String ticketId,
required VoidCallback onDeclineConfirmed}) {
showDialog(
context: context,
builder: (context) {
return StateFulWidgetYouCreate();
});
}

TextFormField and Keyboard in flutter

Good morning, all, I've a problem. I've a text Form Field in my application when I tapped on it to type, the keyboard didn't show this video explain what happened exactly
I read questions that have same problem and try to do its answer but no way
solutions that I tried it.
1- convert the stateless to stateful and the oppsite
2- declare controller global (after imports - in cubit file - in shared file)
3-don't use Form Widget
4- don't use onFieldSubmitted properity
5- try to run flutter run --release but nothing shown in terminal
6- check crashes in google play console
probably I tried most of answers, but no one is working for me,
this is the code of my search screen
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class SearchScreen extends StatefulWidget {
const SearchScreen({Key? key}) : super(key: key);
#override
State<SearchScreen> createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
var width = size.width;
return BlocConsumer<HomeCubit, HomeStates>(
listener: (context, state) {},
builder: (context, state) {
var cubit = HomeCubit.get(context);
return Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Row(
children: [
// which zone text
SizedBox(
width: width * .30,
child: FittedBox(child: Text('اختر المنطقة',style: TextStyle(fontSize: 24,fontWeight:FontWeight.normal))))
const SizedBox(width: 25.0),
Expanded(
child: Center(
child: DropdownButton(
alignment: Alignment.center,
value: cubit.zonePopupValue,
hint: const Text('كل المناطق', textAlign: TextAlign.center),
style: TextStyle(
color: HexColor('#ECB365'),
fontSize: 24,
fontWeight: FontWeight.normal,
),
items: cubit.list,
onChanged: (value) => cubit.changePopupZoneValue(int.parse(value.toString())),
),
),
),
],
)
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: TextFormField(
textInputAction: TextInputAction.search,
onFieldSubmitted: (_) async => cubit.search(),
controller: cubit.searchController,
keyboardType: TextInputType.text,
maxLines: 1,
maxLength: 100,
textAlign: TextAlign.right,
textDirection: TextDirection.rtl,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(right: 20, left: 10),
labelText: "إبحث",
alignLabelWithHint: true,
labelStyle: TextStyle(
color: HexColor('#ECB365'),
fontSize: 24,
fontWeight: FontWeight.normal,
),
suffixIcon: cubit.isLoadingAuth? CircularProgressIndicator(color: HexColor('#ECB365')): IconButton(
onPressed: () => cubit.search(),
icon: Icon(Icons.search,color: HexColor('#ECB365')),
iconSize: 35,
color: HexColor('#081C31'),
),
border: OutlineInputBorder(borderSide:BorderSide(color: HexColor('#ECB365'))),
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: HexColor('#ECB365'))),
enabledBorder: OutlineInputBorder(borderSide:BorderSide(color: HexColor('#ECB365'))),
fillColor: HexColor('#ECB365')
),
)
),
Container(
margin: const EdgeInsets.only(left: 10.0, right: 20.0),
child: const Divider(color: Colors.black,height: 36)
),
cubit.responseBodySearch == null
? const Center(child: Text("أبدأ البحث"))
: cubit.responseBodySearch.isEmpty
? const Center(child: Text("غير متوفر حاليا"))
: ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: cubit.responseBodySearch!.length,
itemBuilder: (context, index) => buildSearchItem(cubit, context, index)
)
],
),
),
)
);
}
);
}
}
this is the code of shared component
class HomeCubit extends Cubit<HomeStates> {
HomeCubit() : super(HomeInitState());
static HomeCubit get(context) => BlocProvider.of(context);
String api = 'https://hadayekhof.com/dalel/api/';
var searchController = TextEditingController();
int? zonePopupValue;
void changePopupZoneValue(int value) {
zonePopupValue = value;
getCategoriesData(zoneIds[value].toString());
emit(HomeChangePopupButtonState());
}
var responseBodySearch;
Future search() async {
emit(HomeSearchLoadingState());
responseBodySubCategories = null;
var data = {
'zone_id': zonePopupValue == null ? '' : zoneIds[zonePopupValue!].toString(),
// 'parent_cat': categoryPopupValue == null ? '' : categoryIds[categoryPopupValue!].toString(),
'term': searchController.text.toString()
};
var uri = Uri.parse('${api}stores/searchStores');
var header = {'Authorization': 'Bearer $ciphertext'};
if (searchController.text == '') {
Fluttertoast.showToast(msg: 'من فضلك اكتب شئ');
} else {
await http.post(uri, body: data, headers:header ).then((value) async {
responseBodySearch = await jsonDecode(value.body);
if (value.body.toString().contains('[') == false) {
responseBodySearch = null;
Fluttertoast.showToast(msg: 'no stores found');
}
emit(HomeSearchSuccessState());
}).catchError((error) {
debugPrint("search error is : $error");
emit(HomeSearchErrorState());
});
}
}
}
how can I solve this problem?
finally, I solve It by write this code in the path android\app\src\main\AndroidManifest.xml
<application
...
android: labelwareAccelerated="true"
...>
and this
<activity
...
android:hardwareAccelerated="true"
...>
It's working now

The following _CastError was thrown building NoteAdder(dirty, state: _NoteAdder#76214):Null check operator used on a null value

The error (given in the title) was thrown when I ran the app.
Here is my code
class NoteAdder extends StatefulWidget {
#override
_NoteAdder createState() => _NoteAdder();
}
class _NoteAdder extends State<NoteAdder> {
Note? note;
TextEditingController titleController = TextEditingController();
TextEditingController descriptionController = TextEditingController();
#override
Widget build(BuildContext context) {
titleController.text = note!.title!;
descriptionController.text = note!.description!;
return AlertDialog(
backgroundColor: Colors.lime,
content: Column(
children: [
const Text(
'ADD NOTE',
style: TextStyle(fontSize: 25),
),
const SizedBox(height: 30),
Container(
alignment: Alignment.topLeft,
child: const Text('Title:'),
),
TextField(
controller: titleController,
decoration: InputDecoration(
border: UnderlineInputBorder(),
),
),
const SizedBox(height: 30),
Container(
alignment: Alignment.topLeft,
child: const Text('Description:'),
),
TextField(
controller: descriptionController,
maxLines: 13,
decoration: InputDecoration(
border: UnderlineInputBorder(),
),
),
const SizedBox(height: 35),
Container(
alignment: Alignment.center,
child: ElevatedButton(
style: ElevatedButton.styleFrom(primary: Colors.red),
onPressed: () {
setState(() {
save();
});
},
child: const Text('Save')))
],
));
}
void save() async {
note?.date = DateFormat.yMMMd().format(DateTime.now()) as DateTime?;
if (note?.id != null) {
await NoteDatabaseHelper.update(note!)??0;
} else {
await NoteDatabaseHelper.insert(note!)??0;
}
}
}
I am a bit new to flutter. Please help me to solve this problem
Link to my complete project: https://github.com/SayanBanerjee09082002/Daily_Utility
Note: The add screen appears when I press a floating action button. The app runs ok until I hit that button.
Since you wrote Note? note;, note == null so trying to use it with null check like this note?.date = DateFormat.yMMMd().format(DateTime.now()) as DateTime?; will throw error. Now I don't know what the constructor of your class Note look like so my answer may not be accurate; but as answer, I will advice you to do either:
Note? note = Note(); //I don't know the structure of the constructor, so you have to deal with that part
or inside save()
if(note != null) {
note.date = DateFormat.yMMMd().format(DateTime.now()) as DateTime;
}

textfield focus triggers rebuilding of UI

when I set Textfield autofocus:false it doesn't refresh the page but when I tap on the TextField, the keyboard shows then the main page rebuilds which causes lag.
This has been a problem for almost a week now. I can find problems related to textfields rebuilding UI but the solution cannot be applied to mine.
MAIN PAGE CONTAINS THIS FUNCTION WHEN A BUTTON IS CLICKED
void addCommentModal() {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: AddCommentModal(
onPost: (String text) {
// APIServices.commentPost(context, i.toString(), text);
Navigator.pop(context);
},
),
);
},
);
}
AddCommentModal
class AddCommentModal extends StatefulWidget {
final ValueChanged<String> onPost;
AddCommentModal({#required this.onPost});
#override
_AddCommentModalState createState() => _AddCommentModalState();
}
class _AddCommentModalState extends State<AddCommentModal> {
final commentController = TextEditingController();
bool _canPost = false;
String defaultProfilePhoto = "";
#override
void initState() {
defaultProfilePhoto = Constants.userFirstName[0].toUpperCase();
super.initState();
}
#override
void dispose() {
commentController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
print("PHOTO: ${Constants.userProfilePhoto}");
return Container(
padding: EdgeInsets.all(10),
child: Row(
children: <Widget>[
Container(
width: 50,
height: 50,
child: ClipRRect(
borderRadius: new BorderRadius.circular(50),
child: Constants.userProfilePhoto == null
? Container(
color: Color(colorPrimary),
alignment: Alignment.center,
child: Text(
defaultProfilePhoto,
style: TextStyle(
color: Color(colorText), fontSize: 20),
),
)
: Image.network(
APIServices.httpDomain + Constants.userProfilePhoto,
fit: BoxFit.cover,
)),
),
Expanded(
child: Container(
margin: EdgeInsets.only(
left: 10,
),
child: TextField(
controller: commentController,
autofocus: true,
decoration: new InputDecoration(
suffixIcon: IconButton(
onPressed: () => widget.onPost(commentController.text),
icon: Icon(
FontAwesomeIcons.paperPlane,
size: 15,
color: Theme.of(context).primaryColor,
)),
contentPadding: EdgeInsets.all(10),
hintText: "Add a comment ...",
fillColor: Colors.white,
border: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(20.0),
),
),
keyboardType: TextInputType.text,
style: new TextStyle(fontFamily: "Poppins", fontSize: 15),
),
))
],
));
}
}
Faced the same issue, been trying for hours :
if your screen depends on MediaQuery or at least having one widget
depenig on MediaQuery, the keyboard popup changes the size of your
screen, which triggers mediaQuery and causing rebuilds...in this case
avoid using mediaQuery, Instead get your dimensions using (sizer
package) https://pub.dev/packages/sizer
Replaced everything related to mediaQuery and now works fine.
It was caused by an unoptimized code on Flutter's SDK: https://github.com/flutter/flutter/issues/37878.
The fix was merged very recently and is on the "master" channel.
Consider switching to that channel, using flutter channel master.