Flutter Circular Progress Indicator On Submit Button - flutter

I have a Login screen and it has two text fields and a submit button, in this submit button, what im doing right now is that when its pressed it shows a loading icon and at some point it will navigate to the other screen, but if the usernames email and phone number is wrong i show a snackbar with and error message, the problem is that even when that happends the loading icon keeps loading and it never stops, what im want to do is that if everything is fine and the user can navigate to the other screen than show the loading icon and then the it will navigate to the other screen, and if the email or password are wrong in this case if response.statusCode is not equal to 200 i show the snackbar with the error message and i want loading icon to be false. same with if the text fields are empty, for now ive made a validator on my TextFormFields, so when the text fields are empty or response.statusCode != 200 loading = false
bool loading = true;
TextFormField loginEmailTextField() {
return TextFormField(
enableInteractiveSelection: false,
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your phone number';
}
return null;
},
TextFormField loginPasswordTextField() {
return TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
},
ElevatedButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
Future<Response> futureResponse = fetchWorkingLocationData();
futureResponse
.then((response) => {
if (response.statusCode == 200)
{
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MenuPage()),
)
}
else
{
{
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
backgroundColor: Colors.blue,
content: Text(
"Incorrect phone number or password",
style: TextStyle(fontSize: 18),
),
duration: Duration(seconds: 4),
),
),
}
},
})
.catchError((error, stackTrace) => print('shush'));
}
if (loading) return;
setState(() {
loading = true;
});
},
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: loading
? Loading()
: Text(
'Submit',

I'm not sure if your isLoading is already outside (it seems so),but isLoading boolean should be outside of your main widget build function, because whenever you call setState your boolean will be set to true again, also I would set it to false instead because you don't want it showing immediately on the login page
In your button function you should also call setState and set your isLoading value to whatever it should be when that if check is done
.then((response) => {
if (response.statusCode == 200)
{
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MenuPage()),
)
}
else
{
{
setState(){
isLoading = false;
}
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
backgroundColor: Colors.blue,
content: Text(
"Incorrect phone number or password",
style: TextStyle(fontSize: 18),
),
duration: Duration(seconds: 4),
),
),
}
},
})
Basically what you want to do is set the state of that boolean whenever it should be set to either true or false
This has to be done for every case that you have in your button (both for the icon and the loading indicator)

Related

how to rebuild dialog Widget on changing bool variable state?

im trying to submit form on Dialog and i have a DateTimePicker button and need to make a validation on it also before submitting , what i want to do is showing a text error in case no date picked by changing my own variable "isValid" to false but the UI is not updating , i have to close the dialog and reopen it to see the error text even though i wrapped my column with a StatefulBuilder
my dialog photo here
here is my code
StatefulBuilder(builder: (context, StateSetter setState) {
return isValid == false
? Column(
children: [
ElevatedButton(
onPressed: () {
DateTimePicker(context)
.then((value) => setState(() {
_appointmentDateTime = value;
}));
},
child: Text(getTimeDate())),
Text(
'error',
style: TextStyle(
color: Colors.red, fontSize: 10),
),
],
)
: Column(
children: [
ElevatedButton(
onPressed: () {
DateTimePicker(context)
.then((value) => setState(() {
_appointmentDateTime = value;
}));
},
child: Text(getTimeDate())),
],
);
})
Validating form + toggling the isValid Value is working fine
OutlinedButton(
onPressed: () async {
if (_formKey.currentState.validate() &&
_appointmentDateTime != null) {
String date = DateFormat('yyyy-MM-dd hh:mm')
.format(_appointmentDateTime);
var appointment = Appointment(
patientName: widget.patient.name,
date: date,
hospital: _hospitalController.text,
await FirebaseApi.addPatientAppointment(
widget.patient.id, appointment);
print('Appointment Created ');
_formKey.currentState.reset();
setState(() {
translator = null;
_appointmentDateTime = null;
});
Navigator.pop(context);
}
else {
setState(() {
isValid = !isValid;
});
}
},
child: Text('Add Appointment')),
It can get confusing when writing the code like this when dealing with Dialogs. The setState you are using in the OutlinedButton is not the same as the setState used in the StatefulBuilder. You need to enclose your OutlinedButton inside the StatefulBuilder too. If you ever use a StatefulBuilder inside a stateful widget, it is better to use a different name like e.g setDialogState.
It is even better to create a separate stateful widget class just for your Dialog contents and pass the formKey and anything else than using a StatefulBuilder in this case to avoid confusion.

Only update UI when there are changes in the server, using streambuilder

I am using streambuilder to check whether a new order is placed or not.
I am checking the order status, if the order status is unknown I want to show a pop up, which works fine. but if i don't select an option to update the order status, streambuilder refreshes after a few seconds, and show another pop up on top of it.
Get Orders Function:
Future<Orders> getOrders() async {
String bsid = widget.bsid;
try {
Map<String, dynamic> body = {
"bsid": bsid,
};
http.Response response = await http.post(
Uri.parse(
"**API HERE**"),
body: body);
Map<String, dynamic> mapData = json.decode(response.body);
Orders myOrders;
print(response.body);
if (response.statusCode == 200) {
print("Success");
myOrders = Orders.fromJson(mapData);
}
return myOrders;
} catch (e) {}
}
Here's the stream function:
Stream<Orders> getOrdersStrem(Duration refreshTime) async* {
while (true) {
await Future.delayed(refreshTime);
yield await getOrders();
}}
StreamBuilder:
StreamBuilder<Orders>(
stream: getOrdersStrem(
Duration(
seconds: 2,
),
),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
CircularProgressIndicator.adaptive(),
);
}
var orders = snapshot.data.statedatas;
return ListView.builder(
itemCount: orders.length,
itemBuilder: (BuildContext context, int index) {
var orderResponse =
snapshot.data.statedatas[index].strAccept;
print(orderResponse);
if (orderResponse == "0") {
print("order status unknown");
Future.delayed(Duration.zero, () {
_playFile();
showCupertinoDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Center(
child: Text(
"#${orders[index].ordrAutoid}",
),
),
content: Row(
children: [
SizedBox(
width: 120,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty
.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(
MaterialState.pressed))
return Colors.black;
return Colors
.green; // Use the component's default.
},
),
),
onPressed: () async {
_stopFile();
Navigator.pop(context);
await changeOrderStatus(
orders[index].orid, "accept");
// setState(() {});
},
child: Text('Accept'),
),
),
SizedBox(
width: 15,
),
SizedBox(
width: 120,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty
.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(
MaterialState.pressed))
return Colors.black;
return Colors
.red; // Use the component's default.
},
),
),
onPressed: () async {
_stopFile();
Navigator.pop(context);
await changeOrderStatus(
orders[index].orid, "Reject");
// setState(() {});
},
child: Text('Reject'),
),
),
// TextButton(
// onPressed: () async {
// _stopFile();
// Navigator.pop(context);
// await changeOrderStatus(
// orders[index].orid, "reject");
// },
// child: Text('reject'),
// ),
],
),
),
);
}).then((value) {
_stopFile();
print("ENDING");
});
}
return Container();
Create a variable to check for the last known order status, outside your if statement, and when a new value comes, compare it to the old value first, then do the if statement logic.
//This is outside the stream builder:
String orderResponseCheck = "";
.
.
.
//This is inside your streambuidler, if the orderResponseCheck is still equal to "", the if statement will be executed,
//and the value of orderResponse wil be assigned to it. This will only show the alert dialog if the orderResponse status changes from the one that previously triggered it.
var orderResponse =snapshot.data.statedatas[index].strAccept;
print(orderResponse);
if (orderResponseCheck != orderResponse && orderResponse == "0") {
orderResponseCheck = orderResponse;
.
.
.
//logic same as before
You shouldn't call showCupertinoDialog (and probably _playFile()) from your build method. Wrapping it with Future.delayed(Duration.zero, () { ... }) was probably a workaround for an error that was given by the framework.
The build method can get executed multiple times. You probably want a way to run _playFile and show the dialog that isn't depending on the UI. I don't think StreamBuilder is the right solution for this.
You could use a StatefulWidget and execute listen on a stream from the initState method. initState will only be called once.
From what I'm reading, you're querying your API every two seconds.
Every time your API answers, you're pushing the new datas to your StreamBuilder, which explains why you're having multiple pop-ups are stacking.
One simple solution to your problem would be to have a boolean set to true when the dialog is displayed to avoid showing it multiple times.
bool isDialogShowing = false;
...
if (orderResponse == "0" && !isDialogShowing) {
isDialogShowing = true;
...
But there are a few mistakes in your code that you should avoid like :
Infinite loops
Querying your API multiple times automatically (it could DDOS your service if plenty of users are using your app at the same time)
Showing your Dialog in a ListView builder

Why AutoCompleteTextField is not showing any suggestion in Flutter?

I am new to Flutter and currently working on a project where I need to show user a list of matched members so that a user can easily select one of them. For that I use AutoCompleteTextField. It is working fine as long as provided by already fetched list of members to it's suggestion property. But I wonder, why it's not working when I put it under BlocBuilder. Event hits on textChanged method and the state also returns a list but the suggestions are invisible.
Widget autoCompleteSearchBar() {
return BlocBuilder<OrderInfoBloc, MyOrderInfoStates>(
builder: (context, state) {
return AutoCompleteTextField<Member>(
clearOnSubmit: false,
style: TextStyle(
color: Colors.black,
fontSize: 16,
),
decoration: InputDecoration(
hintText: 'Search Member Here..',
border: InputBorder.none,
suffixIcon: IconButton(
icon: Icon(Icons.cancel),
iconSize: 20,
color: Colors.yellow[700],
onPressed: () {
_autoCompleteController.text = "";
},
),
contentPadding: EdgeInsets.fromLTRB(10, 30, 10, 20),
hintStyle: TextStyle(color: Colors.grey),
),
keyboardType: TextInputType.text,
controller: _autoCompleteController,
textChanged: (value) {
context.read<OrderInfoBloc>().add(SearchTextChanged(text: value));
},
itemSubmitted: (item) async {
_autoCompleteController.text = state.radioGroupValue == 'By Code'
? item.memberNo
: item.memberName;
context.read<OrderInfoBloc>().add(SelectedMember(member: item));
},
key: _key,
suggestions: state.membersList,
itemBuilder: (context, item) {
print(item);
// return state.radioGroupValue == 'By Code'
// ? autoCompleteSearchBarRow(
// item: item.memberNo, icon: Icon(Icons.person))
// : autoCompleteSearchBarRow(
// item: item.memberName, icon: Icon(Icons.person));
return autoCompleteSearchBarRow(
item: item.memberNo, icon: Icon(Icons.person));
},
itemFilter: (item, query) {
print(query);
// bool _itemFilter;
// if (_autoCompleteController.text.isNotEmpty) {
// _itemFilter = state.radioGroupValue == 'By Code'
// ? item.memberNo
// .toLowerCase()
// .startsWith(query.toLowerCase())
// : item.memberName
// .toLowerCase()
// .startsWith(query.toLowerCase());
// } else {
// _autoCompleteController.text = '';
// _itemFilter = false;
// }
// return _itemFilter;
return item.memberNo.toLowerCase().startsWith(query.toLowerCase());
},
itemSorter: (a, b) {
// return state.radioGroupValue == 'By Code'
// ? a.memberNo.compareTo(b.memberNo.toLowerCase())
// : a.memberName.compareTo(b.memberName.toLowerCase());
print(b);
return a.memberNo.compareTo(b.memberNo.toLowerCase());
},
);
}
);
}
Widget autoCompleteSearchBarRow(
{#required String item, #required Icon icon}) {
return ListTile(
leading: icon,
title: Text(item),
);
}
Use the flutter_typeahead package which works well with flutter bloc
Now, come to the bloc side you don't need to wrap your autocomplete widget with blocbuilder cause if you do so, the bloc will always repaint the widget whenever an event fires. so in your case when you are typing in the text box, event fires and bloc rebuild the widget and because of that suggestion don't show up and even if you see suggestion they will be gone once the corresponding bloc state occurs and rebuild the widget
the recommended solution would be seen below
Don't add any state to get suggestions just return the result or records from event as below. (below function added to Cubit file)
Future<List<Item>> getProductItemsBySearchString(String item) async {
return await itemRepository.getItemsByName(item);
}
as you can see above I am returning item records directly from the getProductItemsBySearchString() event method (no bloc state)
Then use It like below
class ItemScreen extends StatelessWidget {
// then you can call bloc event in function as below
Future<List<Item>> getItemSuggestionsList(
BuildContext context, String text) async {
final bloc = context.read<ItemCubit>();
List<Item> data = await bloc.getProductItemsBySearchString(text);
if (data != null) {
return data;
} else {
return null;
}
}
#override
Widget build(BuildContext context) {
return TypeAheadField(
getImmediateSuggestions: true,
textFieldConfiguration: TextFieldConfiguration(
controller: _itemEditingController,
autofocus: false),
suggestionsCallback: (pattern) {
// call the function to get suggestions based on text entered
return getItemSuggestionsList(context, pattern);
},
itemBuilder: (context, suggestion) {
// show suggection list
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Text(suggestion.name),
trailing: Text(
'Item Code: ${suggestion.code}',
),
),
);
},
onSuggestionSelected: (suggestion) {
// get selected suggesion
},
);
}
}

Flutter TextField calls onSubmitted unexpectedly

My case is I have a widget with TextField used for search. When I type something in TextField the cross icon become visible in suffixIcon (to clear). I do not do search and just click the cross icon to clear the entered input but onSubmitted is called and search executed!!! But I don't need it! I do not submit the text input, I cancel it!!
final searchClear = ValueNotifier(false);
final searchController = TextEditingController();
// in initState method:
searchController.addListener(() {
searchClear.value = searchController.text.isNotEmpty;
});
// in build method:
TextField(
...
controller: searchController,
suffixIcon: ValueListenableBuilder<bool>(
valueListenable: searchClear,
builder: (_,visible,child) {
return Visibility(
visible: visible,
child:child,
);
},
child: InkWell(
child: Icon(Icons.close),
onTap: () {
searchController.clear();
searchFocus.unfocus();
}
),
),
onSubmitted: (value) {
if(value.isEmpty) {
FocusScope.of(context).requestFocus(searchFocus);
} else {
widget.search(value);
}
}
),
P.S. Any ideas to work around this?

How to handle null value in TextField. [Flutter]

In this app there are two screens, the first screen displays all the user input Strings in a List, and the second screen has a TextField which takes the user input which will return to the first screen using onEditingComplete. The user will be able to navigate to the second screen by using a FAB in the first screen. When the user does not type anything in the TextField and returns to the previous page, the String which stores the user input value returns "null". I have added an if statement which will only return to the first page if the TextField is not empty but when i click on submit, it does not navigate to the first screen at all.
Could i get a suggestion on how i can modify the code so that null value from the TextField will not save to the String and navigate back to the first screen.
FAB which navigates to input(second) screen:
FloatingActionButton(
onPressed: ()async {
// setstring(String result){
// widget.updatestring = result;
// return widget.updatestring;
// }
String result1 = await Navigator.push( // string which stores the user entered value
context,
MaterialPageRoute(
builder: (context) => InputScreen(), //screen which has TextField
));
setState(() {
// widget.updatestring = result1;
TodoList(result1);
// setstring(result1);
addItem(result1, false); // function which adds the entered task in a list
});
},
heroTag: "btn2",
child: Icon(Icons.add, color: Color(whitecolor),), backgroundColor: Color(redcolor),),
Textfield which takes user input :
TextField(
autofocus: true,
onEditingComplete: (){
String textToSendBack = taskcontroller.text;
if(taskcontroller.text.isNotEmpty) {
Navigator.pop(context, textToSendBack);
}
},
// onSubmitted: (value) {
// String textToSendBack = taskcontroller.text;
// Navigator.pop(context, value);
// },
maxLength: 100,
controller: taskcontroller,
decoration: InputDecoration(
labelText: "enter tasks here"
),
style: TextStyle(height: 1.2, fontSize: 20, color: Colors.black87),
)
For me it's working fine isEmpty or isNotEmpty
_titleController.text.isEmpty
_titleController.text.isNotEmpty
you can do something like this:
String textToSendBack = taskcontroller.text;
if(textToSendBack != null){
Navigator.pop(context, textToSendBack);
}
that will only take you back to the other screen when the Text isn't empty. otherwise, you stay on the current screen waiting for valid input.
I hope I understood you question correctly.