Show a loading spinner when sending OTP using Firebase - flutter

I want to show a loading spinner whenever user clicks on "SEND OTP" button. I have created a variable _IsLoading which I am setting to true just prior to calling the verifyphone function and then setting it to false afterwards. It seems like it does not have any effect whatsoever on the UI. How can I make this work?
If I replace the verifyPhone with the following line of code, then I do see the spinner :
await new Future.delayed(new Duration(milliseconds: 1500));
I have disabled recaptcha, so my app does not navigate to the recaptcha verification page. But still the spinner does not work.
#override
Widget build(BuildContext context) {
return _isLoading
? Center(child: Loading()):Scaffold(
appBar: AppBar(
title: Text("OTP Verification"),
),
body:SizedBox(
width: double.infinity,
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(20)),
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: SizeConfig.screenHeight * 0.05),
Text(
"OTP Verification",
style: headingStyle,
),
Text("Press Send OTP to get a 6 digit code via SMS and enter below.."),
// buildTimer(),
//OtpForm(),
Form(
child: Column(
children: [
SizedBox(height: SizeConfig.screenHeight * 0.15),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: getProportionateScreenWidth(40),
child: TextFormField(
autofocus: true,
maxLength: 1,
obscureText: true,
style: TextStyle(fontSize: 24),
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
decoration: otpInputDecoration,
onChanged: (value) {
nextField(value, pin2FocusNode);
},
),
),
SizedBox(
width: getProportionateScreenWidth(40),
child: TextFormField(
focusNode: pin2FocusNode,
maxLength: 1,
obscureText: true,
style: TextStyle(fontSize: 24),
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
decoration: otpInputDecoration,
onChanged: (value) => nextField(value, pin3FocusNode),
),
),
SizedBox(
width: getProportionateScreenWidth(40),
child: TextFormField(
focusNode: pin3FocusNode,
maxLength: 1,
obscureText: true,
style: TextStyle(fontSize: 24),
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
decoration: otpInputDecoration,
onChanged: (value) => nextField(value, pin4FocusNode),
),
),
SizedBox(
width: getProportionateScreenWidth(40),
child: TextFormField(
maxLength: 1,
focusNode: pin4FocusNode,
obscureText: true,
style: TextStyle(fontSize: 24),
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
decoration: otpInputDecoration,
onChanged: (value) => nextField(value, pin5FocusNode),
),
),
SizedBox(
width: getProportionateScreenWidth(40),
child: TextFormField(
autofocus: true,
focusNode: pin5FocusNode,
obscureText: true,
maxLength: 1,
style: TextStyle(fontSize: 24),
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
decoration: otpInputDecoration,
onChanged: (value) {
nextField(value, pin6FocusNode);
},
),
),
SizedBox(
width: getProportionateScreenWidth(40),
child: TextFormField(
autofocus: true,
focusNode: pin6FocusNode,
maxLength: 1,
obscureText: true,
style: TextStyle(fontSize: 24),
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
decoration: otpInputDecoration,
onChanged: (value) {
if (value.length == 1) {
pin6FocusNode.unfocus();
// Then you need to check is the code is correct or not
}
}
),
),
],
),
SizedBox(height: SizeConfig.screenHeight * 0.15),
DefaultButton(
text: _isLoading?"Sending OTP...":"Send OTP",
press: () async {
setState(() {
_isLoading = true;
});
verifyPhoneNumber();
setState(() {
_isLoading = false;
});
},
),
SizedBox(),
DefaultButton(
text: "Continue",
press: () async {
},
)
],
),
),
SizedBox(height: SizeConfig.screenHeight * 0.1),
GestureDetector(
onTap: () {
// OTP code resend
},
child: Text(
"Resend OTP Code",
style: TextStyle(decoration: TextDecoration.underline),
),
)
],
),
),
),
));
}

I found a solution. You can register a callback in the verifyPhoneNumber function by using then
Right before calling verifyPhoneNumber function, add this:
setState(() {
_isLoading = true;
});
Then in the function's call back, add change _isLoading to false.
await _auth.verifyPhoneNumber(
phoneNumber: '+11345670033',
timeout: const Duration(seconds: 5),
verificationCompleted: verificationCompleted,
verificationFailed: verificationFailed,
codeSent: codeSent,
codeAutoRetrievalTimeout:
codeAutoRetrievalTimeout).then((value) {
setState(() {
_isLoading = false;
});
});

Related

Flutter post to API from Textfield and DropdownButton

I have the following form to post data to api. the form has textfields and dropdownbuttons as follows. How can I map to the API and post to api?
Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 50.0),
child: Form(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
TextFormField(
decoration: InputDecoration(hintText: 'Job Title'),
controller: titleController,
onChanged: (val) {},
),
SizedBox(height: 20.0),
TextFormField(
decoration: InputDecoration(hintText: 'Company'),
controller: companyController,
onChanged: (val) {},
),
DropdownButton(
value: currentValue,
icon: const Icon(Icons.keyboard_arrow_down),
items: <String>[
"1",
"2",
"3",
].map((String i) {
return DropdownMenuItem(
value: i,
child: Text(i),
);
}).toList(),
onChanged: onChangedCallback,
),
SizedBox(height: 20.0),
DropdownSearch<String>(
mode: Mode.MENU,
showSearchBox: false,
showClearButton: true,
items: ["Aviation", "Arts", "Business"],
dropdownSearchDecoration: InputDecoration(
labelText: "Category",
hintText: "Select Job Category",
),
onChanged: print,
),
SizedBox(height: 20.0),
TextFormField(
decoration: InputDecoration(hintText: 'Location'),
onChanged: (val) {},
),
SizedBox(height: 20.0),
TextFormField(
maxLines: 3,
decoration: InputDecoration(hintText: 'Description'),
onChanged: (val) {},
),
SizedBox(height: 20.0),
TextFormField(
decoration: InputDecoration(hintText: 'Salary'),
onChanged: (val) {},
),
SizedBox(height: 30.0),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Color.fromARGB(255, 252, 114, 2)),
onPressed: () {
},
child: Text(
"Add",
style:
TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
)),
],
),
),
),
),
I want the textfields to get data and post to api while dropdownbuttons have an item and post the item with the corresponding item name on the api? (Like from the following dropdown buttton DropdownButton( value: currentValue, icon: const Icon(Icons.keyboard_arrow_down), items: <String>[ "1", "2", "3", ].map((String i) { return DropdownMenuItem( value: i, child: Text(i), ); }).toList(), onChanged: onChangedCallback, ),
if I select 1, it will post 1 to the api and the same for category. How can this be implmented? )
First of all you need to use an HTTP client package like Dio or tttp
Then you will need to make an api call using this package whenever you click the button.
So an example using Dio package would be like that
import 'package:dio/dio.dart';
void postHttp() async {
try {
var response = await Dio().post('your-api-link',data: {'title': "job title field", 'currentValue': 'current value field'});
print(response);
} catch (e) {
print(e);
}
}
And you will find more info in the package documentation.
Quick hint:
try to test your api using postman first to make sure it works correctly

Keyboard disappearing when moving the focus to the next TextFormField flutter

This the form where I'm trying to move the focus. All is working fine till the field title, where I want to move it to a textField phone number but in doing so instead of moving the focus the soft keyboard disappears. I tried to attached scrollController to the SingleScrollView and then move it in onSaved in the textfield before this one I want to get focus:
controller.singleSclollViewController.jumpTo(controller.singleSclollViewController.position.maxScrollExtent);
But it is doing nothing.
This is the complete form with that problem
Form(
key: controller.formKey,
child: SingleChildScrollView(
controller: controller.singleSclollViewController,
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
verticalSpaceMedium,
Image.asset(
'assets/graphics/data.png',
),
Container(
padding: EdgeInsets.symmetric(horizontal: 40),
child: GetBuilder<HomeController>(
builder: (builderController) =>
builderController.isPosition
? CustomTextField(
autofocus: true,
focusNode:
controller.adressFocusNode,
validate: (text) => controller
.validateTextField(text),
maxline: 4,
minLine: 2,
height: 80.h,
width: 0.8.sw,
controller:
controller.adressController,
color:
AppColors.primaryColorShade,
//adress
lableText: 'адреса',
)
: CircularProgressIndicator()),
),
verticalSpaceSmall,
Container(
padding: EdgeInsets.symmetric(
horizontal: 40,
vertical: 10,
),
child: TypeAheadFormField<City>(
validator: (text) {
if (globalController
.doesSuggestionExist) {
//there is no such available city
return 'такого доступного міста немає';
}
return controller
.validateTextField(text ?? '');
},
textFieldConfiguration:
TextFieldConfiguration(
controller: controller.cityController,
decoration:
outlineInputTextFormFieldStyle!
.copyWith(
label: Text(
//city
'місто',
style: textfieldLableStyle,
)),
),
onSuggestionSelected: (City city) {
controller.cityController.text =
city.name;
controller.nameFocusNode.requestFocus();
},
itemBuilder: (_, City city) {
return ListTile(
leading: Icon(Icons.location_city),
title: Text(
city.name,
style: headingBlackStyle,
),
);
},
suggestionsCallback: (pattern) async {
return await globalController
.getSuggestions(pattern, '');
}),
),
verticalSpaceSmall,
OneLineTextField(
focusNode: controller.nameFocusNode,
onSubmit: () {
controller.nameFocusNode.unfocus();
controller.titleFocusNode.requestFocus();
},
keybordhType: TextInputType.name,
validator: (text) {
return controller
.validateTextField(text ?? '');
},
//name/"Ім'я та прізвище"
lable: "Ім'я та прізвище",
maxLenght: 25,
controller: controller.nameController),
verticalSpaceSmall,
OneLineTextField(
onSubmit: () {
controller.singleSclollViewController
.jumpTo(controller
.singleSclollViewController
.position
.maxScrollExtent);
controller.phoneFocusNode.requestFocus();
},
focusNode: controller.titleFocusNode,
maxLenght: 25,
keybordhType: TextInputType.name,
validator: (text) {
return controller
.validateTextField(text ?? '');
},
//title/"потрібен титул"
lable: 'Назва оголошення',
controller: controller.titleController),
verticalSpaceSmall,
OneLineTextField(
focusNode: controller.phoneFocusNode,
onSubmit: () => controller
.descripotionFocusNode
.requestFocus(),
maxLenght: 15,
keybordhType: TextInputType.number,
validator: (text) {
return controller
.validateTextField(text ?? '');
},
//phone number/ "телефонний номер"
lable: 'телефонний номер',
controller:
controller.contactNumberController),
verticalSpaceSmall,
Container(
padding: EdgeInsets.symmetric(horizontal: 40),
child: CustomTextField(
onSubmit: () => controller
.descripotionFocusNode
.unfocus(),
focusNode: controller.descripotionFocusNode,
maxLenght: 400,
validate: (text) =>
controller.validateTextField(text),
maxline: 10,
minLine: 5,
height: 120.h,
width: 0.8.sw,
controller: controller.descriptionController,
color: AppColors.primaryColorShade,
//description
lableText: 'опис',
),
),
],
),
),
),

Issue with Login form Layout in Flutter

I'm making a login form in Flutter, I'm using a ListView that has a Container as a child and it's child is a Stack widget which has Positioned children. Because of the Stack, I have to have a bound height, so hence the Container widget, which I gave the height: height: MediaQuery.of(context).size.height * 1.2. If I remove the * 1.2 my Button and and Text widget don't show up, and when I click on the login with * 1.2, my validator pops up, red warning signs shows that your info is entered incorrectly, so I can't see the button anymore. Example in pictures:
This is with height: MediaQuery.of(context).size.height * 1.2
Then I try to login, validator pops, and now I can't see the button nor the text and link below the button:
The problem I am facing is, how do I layout this login form that can only be scrollable as far as it needs, so I don't have empty space after the Button, just to spread the form so it is visible, not get something like this if I increment the height of the Container?
Code:
ListView(
shrinkWrap: true,
children: [
Container(
height: MediaQuery.of(context).size.height * 1.6,
child: Stack(
children: [
Positioned(
width: MediaQuery.of(context).size.width,
top: MediaQuery.of(context).size.width * 0.1,
child: Image.asset(
'assests/images/loginform.png',
scale: 2.5,
height: 60,
width: 119,
),
),
Positioned(
width: MediaQuery.of(context).size.width,
top: MediaQuery.of(context).size.width * 0.35,
child: Padding(
padding: const EdgeInsets.only(left: 24.0, right: 25.0),
child: Column(
children: <Widget>[
Form(
key: _registrationFormKey,
child: Column(
children: [
TextFormField(
textAlign: TextAlign.start,
onChanged: (value) {
context.read<User>().name = value;
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Full name'),
validator: (thisValue) {
if (thisValue.isEmpty) {
return 'Please enter your full name';
}
return null;
},
),
SizedBox(
height: 24.0,
),
TextFormField(
keyboardType: TextInputType.phone,
textAlign: TextAlign.start,
onChanged: (value) {
context.read<User>().phoneNumber = value;
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Phone number'),
validator: _validateMobile,
),
SizedBox(
height: 24.0,
),
TextFormField(
keyboardType: TextInputType.streetAddress,
textAlign: TextAlign.start,
onChanged: (value) {
context.read<User>().address = value;
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Address'),
validator: (thisValue) {
if (thisValue.isEmpty) {
return 'Please enter your address';
}
return null;
},
),
SizedBox(
height: 24.0,
),
TextFormField(
keyboardType: TextInputType.text,
textAlign: TextAlign.start,
onChanged: (value) {
context.read<User>().companyName = value;
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Company name'),
validator: (thisValue) {
if (thisValue.isEmpty) {
return 'Please enter your company name';
}
return null;
},
),
SizedBox(
height: 24.0,
),
TextFormField(
keyboardType: TextInputType.text,
textAlign: TextAlign.start,
onChanged: (value) {
context.read<User>().website = value;
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Website name'),
validator: _validateWebsite),
SizedBox(
height: 24.0,
),
TextFormField(
keyboardType: TextInputType.emailAddress,
textAlign: TextAlign.start,
onChanged: (value) {
context.read<User>().email = value;
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'E-mail address'),
validator: _validateEmail,
),
SizedBox(
height: 24.0,
),
TextFormField(
keyboardType: TextInputType.text,
obscureText: _obscureText,
textAlign: TextAlign.start,
onChanged: (value) {
context.read<User>().password = value;
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Password',
suffixIcon: IconButton(
icon: const Icon(Icons.visibility_outlined),
onPressed: _togglePassVisibility,
),
),
validator: _validatePassword,
),
],
),
),
FormField<bool>(
// 1
initialValue: _agree,
builder: (FormFieldState<bool> state) {
// 2
return Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Checkbox(
// 3
value: state.value,
onChanged: (bool val) => setState(() {
// 4
_agree = val;
// 5
state.didChange(val);
}),
),
const Text('I agree with'),
TextButton(
onPressed: () {},
child: Text('Terms and conditions'),
),
],
),
// 6
state.errorText == null
? Text("")
: Text(state.errorText,
style: TextStyle(color: Colors.red)),
],
);
},
// 7
validator: (val) => _validateTerms(_agree),
),
AlarmButtons(
buttonColour: Color(0xFF29ABE2),
buttonText: 'CREATE ACCOUNT',
buttonTextColour: Colors.white,
buttonBorderSide: Colors.white,
onButtonPress: () async {
if (_registrationFormKey.currentState.validate()) {
signUpToCognito(context);
Navigator.pushNamed(
context, ConfirmRegistrationScreen.id);
}
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Already have an account?'),
TextButton(
onPressed: () {
Navigator.pushNamed(context, LoginScreen.id);
},
child: Text('Log in'),
),
],
),
],
),
),
),
],
),
)
],
)
Thanks in advance for the help and suggestions!
there are a couple mistakes (or at least optimizations) in your code. I will go over them one by one:
If you have a small number of children that need to be scrolled, it is better to use a SingleChildScrollView with a Column instead of a list view. ListView builds its children lazily - that is, it only builds widgets that are visible on the screen. If you have only a handful of widgets with no complex animations, then you don't really need that. SingleChildScrollView is more flexible that a ListView.
SingleChildScrollView(
child: Column(
children: [...]
)
)
It seems that you want the image to be on the background with the form validation on top of it. For that, you used a Stack and a Column as children to a ListView. Instead, have the Stack as a parent, with both the ListView and the image as children to the Stack. Now this might produce an error, as the ListView might expand infinitely. A simple solution is to wrap it within a Positioned widget with all its sides set to zero.
Stack(
children: [
BackgroundImageWidget(),
Positioned(
top: 0, bottom: 0, left: 0, right: 0, // or width: screenWidth, height: screenHeight,
child: ListView(...),
)
]
)

Flutter - How to get value in TextFormField using Navigator

Container(
padding: EdgeInsets.all(15),
child: Column(
children: [
TextFormField(
onTap: () =>
Navigator.of(context).pushNamed(AirportSearchScreen.id).then(
(value)
{
setState(() {
_initValues['departureCity'] = value;
print(_initValues['departureCity']);
});
},
),
decoration: InputDecoration(
labelText: 'Departure City',
),
initialValue: _initValues['departureCity'],
),
],
),
);
When I am printing the value, it is give right result. But I am not able to get the result on TextFormField.
Try the code below :
child: Column(
children: [
TextFormField(
onTap: () =>
Navigator.of(context).pushNamed(AirportSearchScreen.id).then(
(value)
{
setState(() {
_initValues['departureCity'] = value;
print(_initValues['departureCity']);
});
},
),
decoration: InputDecoration(
labelText: 'Departure City',
),
controller: TextEditingController(text: _initValues['departureCity']),
),
],
),

How to fix "Another exception was thrown: NoSuchMethodError: The getter 'value' was called on null" error

I was trying to create a register page for my app, but when i load the page i got this error
I'm working with Flutter in Visual Studio code
Widget build(BuildContext context) {
TextFormField email = new TextFormField(
validator: (value) {
Pattern pattern =
r'^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regex = new RegExp(pattern);
if (!regex.hasMatch(value))
return 'Ingrese un correo valido';
else
return null;
},
controller: emailcontroller,
decoration: InputDecoration(
hintText: 'Email',
prefixIcon: Icon(Icons.account_circle),
border: _textFieldBorder,
),
style: TextStyle(),
textAlign: TextAlign.left,
);
TextFormField name = new TextFormField(
controller: namecontroller,
decoration: InputDecoration(
hintText: 'Nombre',
prefixIcon: Icon(Icons.person_pin),
border: _textFieldBorder,
),
style: TextStyle(),
textAlign: TextAlign.left,
validator: (value) {
if (value.isEmpty) {
return 'Este campo no puede estar vacio';
} else
return null;
},
);
TextFormField cellphone = new TextFormField(
maxLength: 9,
controller: phonecontroller,
decoration: InputDecoration(
hintText: 'Phone',
prefixIcon: Icon(Icons.phone),
border: _textFieldBorder,
),
style: TextStyle(),
textAlign: TextAlign.left,
);
TextFormField password = new TextFormField(
controller: passwordcontroller,
obscureText: true,
decoration: InputDecoration(
hintText: 'Contraseña',
prefixIcon: Icon(Icons.lock),
border: _textFieldBorder,
),
style: TextStyle(),
textAlign: TextAlign.left,
);
TextFormField confirmpassword = new TextFormField(
obscureText: true,
decoration: InputDecoration(
hintText: 'Repita la contraseña',
prefixIcon: Icon(Icons.lock),
border: _textFieldBorder,
),
style: TextStyle(),
textAlign: TextAlign.left,
validator: (value) {
if (value == passwordcontroller.text) {
return null;
} else {
return 'Las contraseñas no son iguales';
}
},
);
return Scaffold(
appBar: AppBar(
title: Text(
'Registro',
style: TextStyle(fontSize: 35),
),
backgroundColor: Color(0xff2196F3),
centerTitle: true,
),
body: Mutation(
options: MutationOptions(
document: query,
),
builder: (RunMutation insert, QueryResult result) {
return new SingleChildScrollView(
child: Form(
key: _formKey,
child: Center(
child: Container(
height: MediaQuery.of(context).size.height / 1.1,
child: Padding(
padding: EdgeInsets.fromLTRB(30, 0, 30, 0),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
email,
SizedBox(height: 8),
name,
SizedBox(height: 8),
cellphone,
SizedBox(height: 8),
password,
SizedBox(height: 8),
confirmpassword,
Divider(height: 40),
GestureDetector(
onTap: () {
if (_formKey.currentState.validate()) {
insert(<String, dynamic>{
"name": namecontroller.text,
"phone": phonecontroller.text,
"password": passwordcontroller.text,
"email": emailcontroller.text
});
Navigator.pop(context);
}
},
child: new Container(
height: 50,
width: MediaQuery.of(context).size.width / 1.2,
decoration: BoxDecoration(
color: Color(0xff2196F3),
border: Border.all(
width: 1.0, color: Colors.transparent),
borderRadius: BorderRadius.all(
Radius.circular(10.0)
), ),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text('Registrarse', style: _loginTextStyle)
],
),
),
)
],
),
),
),
),
),
);
}));
}
I expect to see the register page but the only thing i get is this error "Another exception was thrown: NoSuchMethodError: The getter 'value' was called on null"
The problem was that i was using a GraphQL client and that client uses an instance of my widget called RegisterPage, and i was trying to navigate to RegisterPage , but the correct thing to do was navigate to my GraphQL client called RegistarPageGraphQL and that client build the RegisterPage