I want to implement OTP verification screen without any package.
when i entered the number it should move to next input field
I m using this code in my current project take a refrence this will help
class Otp extends StatefulWidget {
final String? phnNumber;
final String ? code;
String? from;
Otp({Key ?key, this.phnNumber, this.from, this.code}) : super(key:
key);
#override
_OtpState createState() => _OtpState();
}
class _OtpState extends State<Otp> {
double ? height ;
double ? width;
TextEditingController ? contrller1;
TextEditingController ? contrller2;
TextEditingController ? contrller3;
TextEditingController ? contrller4;
SendOtpRequest resend = SendOtpRequest();
SharedPreferences ? prefs;
getSharedPreferences () async
{
prefs = await SharedPreferences.getInstance();
}
String Code = "";
#override
void initState() {
// TODO: implement initState
super.initState();
contrller1 = TextEditingController();
contrller2 = TextEditingController();
contrller3 = TextEditingController();
contrller4 = TextEditingController();
getSharedPreferences();
}
#override
Widget build(BuildContext context) {
height= MediaQuery.of(context).size.height;
width = MediaQuery.of(context).size.height;
final verifyprovider = Provider.of<PostDataProvider>(context);
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
toolbarHeight:height! * 0.07802345,
titleSpacing: 0,
backgroundColor: HexColor("#18263d"),
automaticallyImplyLeading: false,
leading: Padding(
padding: const EdgeInsets.only(left: 8.0,),
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Container(
color: Colors.transparent,
child: Image.asset("assets/images/back_ic-1.png")),
),
),
// SizedBox(width: width!*0.001234,),
title:Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
height: height!/15,
width: height!/15,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
width: 2,
color:HexColor("#fc4f00"),
)),
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Container(
height: height!/11,
width: height!/11,
decoration: BoxDecoration(
image: const DecorationImage(
image:
AssetImage("assets/images/home_logo.png"),
fit: BoxFit.fill
),
shape: BoxShape.circle,
border: Border.all(
width: 1,
color:HexColor("#fc4f00"),
)),
),
),
),
SizedBox(width: width! * 0.04234,),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text("Verification",
style: GoogleFonts.oswald(fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: width! * 0.03345
),),
),
],
) ,
),
body: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 24, horizontal: 32),
child: Column(
children: [
Text("We have send verification code on your mobile number",
style: GoogleFonts.oswald(fontStyle: FontStyle.normal,
fontSize: width!*0.0234,
color: HexColor("#8b8b8b")),
),
SizedBox(height: height!/38,),
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_textFieldOTP(first: true, last: false,controllerr:
contrller1),
_textFieldOTP(first: false, last: false,controllerr:
contrller2),
_textFieldOTP(first: false, last: false,controllerr:
contrller3),
_textFieldOTP(first: false, last: true, controllerr:
contrller4),
],
),
Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap:() {
resend.phoneNumber= widget.phnNumber;
resend.countryCode = widget.code;
verifyprovider.resendOtp(context,
jsonEncode(resend));
},
child: Text("Resend OTP?",
style: GoogleFonts.oswald(fontStyle:
FontStyle.normal,
fontSize: width!*0.0234,
color: HexColor("#fc4f00")),
),
),
),
SizedBox(height: height!/28,),
GestureDetector(
onTap: (){
if(contrller1!.text.isNotEmpty&&
contrller2!.text.isNotEmpty&&contrller3!.
text.isNotEmpty&&contrller4!.text.isNotEmpty){
verifyOtpRequest verify = verifyOtpRequest();
verify.phoneNumber = widget.phnNumber;
verify.otp=
contrller1!.text+contrller2!.
text+contrller3!.text+contrller4!.text;
verifyprovider.otpVerification(context,
jsonEncode(verify), widget.from);
}else{
CommonUtils.showToast(msg: "Please fill all the
fields ");
}
},
child: Container(
height: height!/18,
width: width,
decoration: BoxDecoration(
color: HexColor("#fc4f00"),
borderRadius: BorderRadius.circular(10)
),
child: Center(
child: Text("Verify",style: TextStyle(
color: Colors.white,
fontSize: width!*0.02345
),),
)
),
),
],
),
],
),
),
),
);
}
Widget _textFieldOTP({bool ? first, last,
TextEditingController ?
controllerr}) {
return Container(
height:height!/12 ,
child: AspectRatio(
aspectRatio: 1.0,
child: TextField(
controller: controllerr,
autofocus: true,
onChanged: (value) {
if (value.length == 1 && last == false) {
FocusScope.of(context).nextFocus();
}
if (value.length == 0 && first == false) {
FocusScope.of(context).previousFocus();
}
},
showCursor: false,
readOnly: false,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
keyboardType: TextInputType.number,
maxLength: 1,
decoration: InputDecoration(
counter: Offstage(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(width: 2, color: Colors.black54),
borderRadius: BorderRadius.circular(12)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(width: 2, color: Colors.black54),
borderRadius: BorderRadius.circular(12)),
),
),
),
);
}
}
When the length of the input data reaches one, you will have to change the text field focus node.
For Example
If you are in the first field, and you enter a number field one focus should be lost, and field two should be in focus. This can be done, by requestFocus.
This article will of help for you: Flutter Focus
Related
I a beginner in Flutter development and I was trying to replicate an animated login screen from this LinkedIn post. I am using Rive animations to make all the states required for the screen possible. A base rive file was available for the screen and after some tweaks, I was able to get the required states.
After implementing the animations in the screen along with two text fields, I noticed an issue with the animations. Whenever I click on the TextField, the soft keyboard pops up. The required animation runs for a short duration then resets to the idle animation. After digging through stack overflow and google and printing debug messages, I noticed that the whole widget rebuilds itself whenever soft keyboard pops up.
Here's the code for my screen -
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({Key? key}) : super(key: key);
#override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
StateMachineController? _controller;
SMIInput<bool>? isChecking;
SMIInput<bool>? trigSuccess;
SMIInput<bool>? trigFail;
SMIInput<bool>? isHandsUp;
SMIInput<double>? numLook;
FocusNode emailFocusNode = FocusNode();
TextEditingController emailController = TextEditingController();
FocusNode passwordFocusNode = FocusNode();
TextEditingController passController = TextEditingController();
#override
void initState() {
super.initState();
emailFocusNode.addListener(emailFocus);
passwordFocusNode.addListener(passFocus);
}
#override
void dispose() {
super.dispose();
emailFocusNode.removeListener(emailFocus);
passwordFocusNode.removeListener(passFocus);
}
void emailFocus() {
isChecking?.change(emailFocusNode.hasFocus);
}
void passFocus() {
isHandsUp?.change(passwordFocusNode.hasFocus);
}
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Color(0xffeef2f3),
// Colors.lightBlueAccent.withBlue(255).withGreen(242).withRed(238),
body: SafeArea(
child: SingleChildScrollView(
child: Container(
width: size.width,
height: size.height,
child: Center(
child: Container(
height: 600,
width: 320,
decoration: BoxDecoration(
color: Color(0xFFffffff),
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 5,
),
],
),
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 25,
horizontal: 25,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
CircleAvatar(
backgroundColor: Colors.black87,
radius: 90,
child: CircleAvatar(
backgroundColor: Colors.transparent,
radius: 88,
child: ClipOval(
child: RiveAnimation.asset(
'assets/login_screen_character.riv',
stateMachines: ["Login Machine"],
onInit: (Artboard artboard) {
_controller =
StateMachineController.fromArtboard(
artboard, "Login Machine");
if (_controller == null) return;
artboard.addController(_controller!);
isChecking =
_controller?.findInput("isChecking");
trigSuccess =
_controller?.findInput("trigSuccess");
trigFail = _controller?.findInput("trigFail");
isHandsUp = _controller?.findInput("isHandsUp");
numLook = _controller?.findInput("numLook");
},
),
),
),
),
SizedBox(
height: 32,
),
Text(
"Email",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xff32779d),
),
),
SizedBox(
height: 4,
),
Container(
decoration: BoxDecoration(
color: Color(0x321fb0d2),
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: Color(0xff1fb0d2),
),
),
child: Padding(
padding: const EdgeInsets.only(
left: 16.0,
right: 16.0,
top: 8,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"email#example.com",
style: TextStyle(
fontSize: 12,
color: Color(0xff32779d),
),
),
TextField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Enter email",
),
focusNode: emailFocusNode,
controller: emailController,
onChanged: (value) {
if (isChecking?.value == false)
isChecking?.change(true);
numLook?.change(value.length.toDouble() * 3);
},
),
],
),
),
),
SizedBox(
height: 32,
),
Text(
"Password",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xff32779d),
),
),
SizedBox(
height: 2,
),
Container(
decoration: BoxDecoration(
color: Color(0x321fb0d2),
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: Color(0xff1fb0d2),
),
),
child: Padding(
padding: const EdgeInsets.only(
left: 16.0,
right: 16.0,
top: 4,
bottom: 4,
),
child: TextField(
onChanged: (value) {
if (isHandsUp?.value == false)
isHandsUp?.change(true);
},
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Enter password",
),
focusNode: passwordFocusNode,
controller: passController,
),
),
),
SizedBox(
height: 32,
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Color(0xff1fb0d2),
),
),
onPressed: () {
if (isHandsUp?.value == true)
isHandsUp?.change(false);
Future.delayed(Duration(milliseconds: 1200), () {
if (passController.text == "thisisatest")
trigSuccess?.change(true);
else
trigFail?.change(true);
});
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
"Log In",
style: TextStyle(
color: Colors.white,
),
),
),
)
],
),
),
),
),
),
),
),
);
}
}
The code is a bit too long, please excuse me for that I haven't really refactored the code.
I've tried using the resizeToAvoidBottomInset: false but animation still resets to the idle state. I've even tried adding the android:windowSoftInputMode="adjustNothing" inside the <activity...> tag but still no result.
I would really like to know if there's any workaround in my code to make the animation states run properly.
I'm trying to display the input value below onto another page that contains radio buttons . my aim is that every time i write or change a value in a textfield it gets updated and displays onto a different page with the radio options . The snippet of code below is for the textfield - i used riverpod to connect it to firestore:
class AddMealPage extends ConsumerWidget {
const AddMealPage({Key? key}) : super(key: key);
static const String route = "/addMeal";
#override
Widget build(BuildContext context, WidgetRef ref) {
final model = ref.read(addMealProvider);
return LoadingLayer(
child: Scaffold(
appBar: AppBar(
elevation: 0,
iconTheme: const IconThemeData(
color: Colors.black,
),
title: const Text(
"Create Meal",
style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
),
backgroundColor: Colors.white,
),
bottomNavigationBar: Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
child: MaterialButton(
padding: const EdgeInsets.all(16),
color: Colors.black,
onPressed: model.enabled
? () async {
try {
await model.writeMeal();
Navigator.pop(context);
} catch (e) {
AppSnackbar(context).error(e);
}
}
: null,
child: const Text(
"Add meal",
style: TextStyle(color: Color.fromARGB(255, 247, 245, 245)),
),
),
),
body: SingleChildScrollView(
child: Container(
padding:
const EdgeInsets.symmetric(vertical: 60.0, horizontal: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
GestureDetector(
onTap: () async {
final picked = await ImagePicker()
.pickImage(source: ImageSource.gallery);
if (picked != null) {
model.file = File(picked.path);
}
},
child: Container(
margin: const EdgeInsets.only(),
width: MediaQuery.of(context).size.width,
height: 210.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
image: (model.mealUrl != null || model.file != null)
? DecorationImage(
image: model.file != null
? FileImage(model.file!)
: NetworkImage(model.mealUrl!)
as ImageProvider,
fit: BoxFit.cover,
)
: null),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (model.mealUrl == null && model.file == null)
const Expanded(
child: Center(
child: Icon(
Icons.photo,
),
),
),
const Material(
color: Colors.transparent,
child: Padding(
padding: EdgeInsets.all(8.0),
),
),
],
),
),
),
const SizedBox(
height: 10,
),
const Padding(
padding: EdgeInsets.only(left: 15.0),
child: Text(
"Meal Name",
style: TextStyle(fontSize: 17, fontWeight: FontWeight.w500),
),
),
Container(
height: 50,
padding: const EdgeInsets.only(top: 10),
width: MediaQuery.of(context).size.width,
margin: const EdgeInsets.symmetric(horizontal: 20),
child: TextFormField(
textInputAction: TextInputAction.done,
initialValue: model.mealName,
maxLines: 1,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Colors.grey),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(color: Colors.grey),
),
),
onChanged: (v) => model.mealName = v,
),
),
I want to display the input value i get from the textfield above in the format of a radioButton on a seperate page :
enum Variant { mealName }
class RadioOption extends StatefulWidget {
const RadioOption({Key? key}) : super(key: key);
#override
State<RadioOption> createState() => _RadioOptionState();
}
class _RadioOptionState extends State<RadioOption> {
Variant? _character = Variant.mealName;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
RadioListTile<Variant>(
title: const Text('$mealName'),
value: Variant.MealName,
groupValue: _character,
onChanged: (Variant? value) {
setState(() {
_character = value;
});
},
),
I don't see the whole architecture of the app. But if you want to transfer model.mealName, you can make String value (mealName) in your RadioOption, and pass it through the constructor:
final mealName;
RadioOption({required this.mealName});
And then access it in _RadioOptionState by:
title: const Text(widget.mealName);
You can use TextEditingController and add this controller to your TextFormField which have controller properties but you can't use initialValue and controller both so avoid initialValue. To put value you can use controller.text = your_value;
You value will be filled in text field.
To get value from TextFormField Use var newValue = controller.text; and send this newValue to another page in constructor.
Screenshot of the issue and code in use below, the selection controls are above the textfield for some bizarre reason.
var textField = TextField(
selectionHeightStyle: BoxHeightStyle.max,
scrollPadding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom + 20 * 4),
textInputAction: TextInputAction.done,
keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
decoration: InputDecoration(
border: InputBorder.none,
),
controller: textEditingController,
onTap: () {
Future.delayed(const Duration(milliseconds: 800), () {
textEditingController.selectAll();
});
},
onSubmitted: (newText) {
if (newText.length == 0) {
callback(0, true);
} else {
callback(int.parse(newText), true);
}
},
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline4!.copyWith(
fontWeight: FontWeight.w700,
fontSize: 16.0,
color: WWColors.pinkBrandColor(),
),
);
return Padding(
padding: padding,
child: Container(alignment: Alignment.center, child: textField),
);
In answer to my own question, this in the TextField attributes did the job in my case:
decoration: InputDecoration(
isDense: true, // important line
contentPadding: EdgeInsets.fromLTRB(0, 5, 0, 5),
border: InputBorder.none,
),
Hi you can try create a custom widget for it to make the customisation as you required below is the simple example for the mentioned implementation.
class CounterWidget extends StatefulWidget {
final double width;
final double height;
final double spaceWidth;
final Color controlColor;
final Color valueColor;
final Function(int count) callback;
const CounterWidget({
Key? key,
required this.title,
required this.callback,
required this.controlColor,
required this.valueColor,
this.width = 50,
this.height = 30,
this.spaceWidth = 5,
}) : super(key: key);
final String title;
#override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Row(
children: [
//Incrementor
InkWell(
onTap: () {
setState(() {
count++;
//Callback method to expose the count to the higher hier archy.
widget.callback(count);
});
},
child: Container(
decoration: BoxDecoration(
color: widget.controlColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(widget.height / 0.1),
bottomLeft: Radius.circular(widget.height / 0.1),
),
),
child: const Center(
child: Icon(
Icons.add,
color: Colors.white,
),
),
width: widget.width,
height: widget.height,
),
),
SizedBox(width: widget.spaceWidth),
Container(
color: widget.valueColor,
width: widget.width,
height: widget.height,
child: Center(
child: Text(
count.toString(),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
),
),
SizedBox(width: widget.spaceWidth),
InkWell(
onTap: () {
if (count > 0) {
setState(() {
count--;
widget.callback(count);
});
}
},
child: Container(
width: widget.width,
height: widget.height,
decoration: BoxDecoration(
color: widget.controlColor,
borderRadius: BorderRadius.only(
topRight: Radius.circular(widget.height / 0.1),
bottomRight: Radius.circular(widget.height / 0.1),
),
),
child: const Center(
child: Icon(
Icons.remove,
color: Colors.white,
),
),
),
),
],
),
);
}
}
So I'm trying to update the value of the drop down list using the setState, but when I select a value from the list, the value on the screen won't be able to change
import 'package:demo_app_admin/Custom/custom_Font.dart';
import 'package:flutter/material.dart';
import '/Custom/Custom_Raised_Bottun.dart';
import '/Custom/Header.dart';
import '/Custom/inputField.dart';
class LoginView extends StatefulWidget {
#override
_LoginViewState createState() => _LoginViewState();
}
class _LoginViewState extends State<LoginView> {
#override
Widget build(BuildContext context) {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final isKeyboard = MediaQuery.of(context).viewInsets.bottom != 0;
String _dropdownvalue = "Global admin";
List<String> _items = <String> ["Global admin", "Institution admin"];
return Scaffold(
body: Container(
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topCenter, colors: [
Colors.teal[700]!,
Colors.teal[200]!,
Colors.teal[400]!
]),
),
child: Column(
children: <Widget>[
const SizedBox(
height: 80,
),
if (!isKeyboard)
Header(
padding: const EdgeInsets.all(20),
crossAxisAlignment: CrossAxisAlignment.start,
head: "Welcome",
headTextStyle:
const TextStyle(color: Colors.white, fontSize: 40),
sizedBoxHeight: 10,
subtitle: "Sign In to Your Admin Account",
subtitleTextStyle:
const TextStyle(color: Colors.white, fontSize: 18),
),
Expanded(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(60),
topRight: Radius.circular(60),
)),
child: Padding(
padding: const EdgeInsets.all(30),
child: SingleChildScrollView(
reverse: true,
padding: EdgeInsets.all(32),
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
const SizedBox(
height: 40,
),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10)),
child: Column(
children: <Widget>[
InputField(
labelText: 'Email',
padding: EdgeInsets.all(10),
borderSide: BorderSide(color: Colors.grey),
hintText: "Enter your email",
color: Colors.grey,
inputBorder: InputBorder.none,
obscureText: false,
enableSuggestion: true,
autoCorrect: true,
onSaved: (value) {},
// (value){controller.email = value!;},
validator: (value) {
if (value == null) {
print("ERROR");
}
},
),
InputField(
labelText: 'Password',
padding: EdgeInsets.all(10),
borderSide: BorderSide(color: Colors.grey),
hintText: "Enter your password",
color: Colors.grey,
inputBorder: InputBorder.none,
obscureText: true,
enableSuggestion: false,
autoCorrect: false,
onSaved: (value) {},
// (value){ controller.password = value!;},
validator: (value) {
if (value == null) {
print("ERROR");
}
},
),
],
),
),
const SizedBox(
height: 40,
),
here is the issue
DropdownButton<String>(
value: _dropdownvalue,
icon: Icon(Icons.keyboard_arrow_down),
items: _items.map((String _items) {
return DropdownMenuItem<String>(
value: _items,
child: CustomFont(text: _items),
);
}).toList(),
onChanged: (value) {
setState(() => _dropdownvalue = value!);
}),
const SizedBox(
height: 40,
),
CustomRaisedButton(
width: 200,
height: 50.0,
margin: const EdgeInsets.symmetric(horizontal: 50),
color: Colors.teal.shade500,
borderRadius: BorderRadius.circular(10),
text: "LOGIN",
textColor: Colors.white,
fontSize: 17,
fontWeight: FontWeight.bold,
function: () {}
// {
// _formKey.currentState?.save();
// if(_formKey.currentState?.validate() !=null){
// controller.signInWithEmailAndPassword();
// }
// },
),
SizedBox(
height: 10,
),
],
),
),
),
),
))
],
),
),
);
}
}
The issue is here, you are declaring variables inside build. Therefore, variables get reset to default value on every build. Means setState.
Declare variables outside the build method.
class _LoginViewState extends State<LoginView> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
String _dropdownvalue = "Global admin";
List<String> _items = <String>["Global admin", "Institution admin"];
#override
Widget build(BuildContext context) {
final isKeyboard = MediaQuery.of(context).viewInsets.bottom != 0;
return Scaffold(
More about StatefulWidget.
I have designed a ui using flutter first I checked it in my phone which is Moto g5s plus the ui looks goods and I occupies entire screen but when I open the same app in iPhone 11 pro max I am getting a huge empty space but when I open it in iPhone 8 then the ui looks fine as it is in Moto g5s plus. I want to know how to have same consistent ui across different device sizes in flutter?
class Login extends StatefulWidget
{
#override
State<StatefulWidget> createState() {
return LoginState();
}
}
class LoginState extends State<Login>
{
String _email,_password;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState()
{
super.initState();
}
Widget _progressBar(AuthProvider authProvider)
{
//print("progress"+authProvider.progressStatus.toString());
return Visibility(
maintainSize: true,
maintainAnimation: true,
maintainState: true,
visible: authProvider.progressStatus,
child: Container(
child: Center(
child: SizedBox(
width: 60,
height: 60,
child: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey,
offset: Offset(0.0, 1.0), //(x,y)
blurRadius: 1.0,
),
],
),
),
Center(
child: CircularProgressIndicator(),
)
],
),
)
)
),
);
}
Widget _emailTextField(AuthProvider authProvider)
{
return TextFormField(
onChanged: (String value){
authProvider.checkEmail(value);
},
decoration: InputDecoration(
labelText: "Email id",
errorText: authProvider.emailStatus ? null : "Enter a valid email",
border: OutlineInputBorder(
borderRadius: new BorderRadius.circular(32.0),
)
),
keyboardType: TextInputType.emailAddress,
validator: (String value){
if(value.isEmpty || !RegExp(r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?").hasMatch(value))
{
return "Enter a valid email";
} else {
return null;
}
},
onSaved: (String value){
_email = value;
},
);
}
Widget _passwordTextField(AuthProvider authProvider)
{
//print("pass check "+authProvider.passStatus.toString());
return TextFormField(
decoration: InputDecoration(labelText: "Password",
filled: true,
errorText: authProvider.passStatus ? null : "Password is too short",
border: OutlineInputBorder(
borderRadius: new BorderRadius.circular(32.0),
),
fillColor: Colors.white
),
keyboardType: TextInputType.text,
obscureText: true,
onChanged: (String value){
authProvider.checkPassword(value);
},
validator: (String value){
if(value.isEmpty)
{
return "Enter a valid password";
}
else if(value.length < 8)
{
return "Password is too short";
}
else
{
return null;
}
},
onSaved: (String value){
_password = value;
},
);
}
void _login(AuthProvider authProvider)
{
if(!_formKey.currentState.validate())
{
return;
}
_formKey.currentState.save();
Map<String,String> map = new Map();
map['email'] = _email;
map['password'] = _password;
authProvider.login(map);
}
#override
Widget build(BuildContext context) {
final authProvider = Provider.of<AuthProvider>(context);
if(authProvider.loginStatus != null && authProvider.loginStatus != "success")
{
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text('${authProvider.loginStatus}',
style: TextStyle(color: Colors.black),),
backgroundColor: Color(0xFFe5e5e5),
));
}
return Scaffold(
key: _scaffoldKey,
body: Stack(
children: <Widget>[
SingleChildScrollView(
child: Container(
child: Column(
children: <Widget>[
Container(
alignment: Alignment.center,
margin: EdgeInsets.fromLTRB(0, 48, 0, 0),
child: Text(
"Take Orders",
style: TextStyle(color: Theme.of(context).primaryColorDark,fontSize: 20),
),
),
Container(
child: Text(
"Track the Best Selling Items",
style: TextStyle(color: Theme.of(context).primaryColor,fontSize: 16),
),
),
Container(
margin: EdgeInsets.fromLTRB(16, 0, 16, 0),
child: SvgPicture.asset('assets/images/undraw_booking.svg',width: 100.0,height: 280.0,),
),
Container(
margin: EdgeInsets.fromLTRB(16, 0, 0, 0),
child:Row(
children: <Widget>[
Text(
"Login To ",
style: TextStyle(color: Colors.black,fontSize: 20)
),
Text(
"Take orders",
style: TextStyle(color: Theme.of(context).primaryColorDark,fontSize: 20),
)
],
),
),
Container(
margin: EdgeInsets.fromLTRB(16, 8, 16, 0),
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
//_emailTextField(_loginBloc),
Container(
margin: EdgeInsets.fromLTRB(0, 8, 0, 0),
child: _emailTextField(authProvider),
),
Container(
margin: EdgeInsets.fromLTRB(0, 8, 0, 0),
child: _passwordTextField(authProvider),
),
Container(
margin: EdgeInsets.fromLTRB(0, 8, 0, 0),
child: Align(
alignment: Alignment.centerLeft,
child: RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(32.0)
),
padding: EdgeInsets.fromLTRB(64, 12, 64, 12),
color: Theme.of(context).accentColor,
textColor: Colors.white,
child: Text(
"Login",
),
onPressed: (){
_login(authProvider);
},
) ,
),
),
],
),
),
),
],
),
)
),
_progressBar(authProvider),
],
),
);
}
}
images of emulator
without seeing code i cannot advise you proper solution however, i guess that tricks will solve your many issues.
instead of using fixed values
try to use
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
you can also use them like
height: MediaQuery.of(context).size.height * 0.80, // 80% of screen height
width: MediaQuery.of(context).size.width * 0.35, // 35% of screen width
Hope will help you