Cannot get proper value in validator in flutter - validation

Steps to Reproduce
Here is the dartpad link go to it.
Enter any values to the Fields calculate the answer.
Add 0. to the start any Field
Expected results: validator should return 0.xyz
Actual results: validator returns 0xyz
Work around found: using onSaved solves this
Can anyone tell me why is it happening or is it bug in flutter then I will a new issue in the flutter repo
I'm printing the value coming from validator and in onChanged parsing string value to int.

Edit
when input 23 and then inert 0. , this line amount = int.parse(val); execute and stop and then click calculate button, so amount is 23 and value's runtimeType is String 023
TextFormField(
keyboardType: TextInputType.number,
validator: (value) {
print("amount $amount");
print(" ${value.runtimeType}");
print("validator amount value $value ");
if (value.isEmpty) {
return "Enter some amount";
} else if (double.parse(value).toInt() <= 0) {
return "Amount should be greater than 0";
}
return null;
},
onChanged: (val){
print("val1 $val");
amount = int.parse(val);
print("amount $amount");
}
You can copy paste run full code below
The reason why validator not return 0.xyz
Because onChanged executed before validator
So validator receive truncated value by int.parse(value)
If you remove amount = int.parse(value); validator will get correct value but
this condition if (int.parse(value) <= 0) will get Invalid radix-10 number
You need to change to if (double.parse(value).toInt() <= 0)
working demo
full code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.purple,
buttonTheme: ButtonThemeData(
buttonColor: Colors.purple.shade400,
),
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: BillSpitApp(),
);
}
}
class BillSpitApp extends StatefulWidget {
BillSpitApp({Key key}) : super(key: key);
#override
_BillSpitAppState createState() => _BillSpitAppState();
}
class _BillSpitAppState extends State<BillSpitApp> {
int amount;
int numOfPersons;
double splitAmount;
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Bill split app"),
),
body: Padding(
padding: const EdgeInsets.all(15.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
splitAmount == null
? "Fill the details and click on calculate to get your bill split"
: "Bill split is ${splitAmount.toStringAsFixed(2)}",
style: TextStyle(
fontSize: 25,
),
textAlign: TextAlign.center,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 15.0),
child: TextFormField(
keyboardType: TextInputType.number,
validator: (value) {
print("validator amount value $value");
if (value.isEmpty) {
return "Enter some amount";
} else if (double.parse(value).toInt() <= 0) {
return "Amount should be greater than 0";
}
return null;
},
onSaved: (value) {
print("onSaved amount value $value");
amount = int.parse(value);
//int.parse(value);
},
decoration: InputDecoration(
labelText: "Amount",
hintText: "1000",
border: OutlineInputBorder(),
),
),
),
TextFormField(
validator: (value) {
print("person value $value");
if (value.isEmpty) {
return "Enter number of persons";
} else if (double.parse(value).toInt() <= 0) {
return "Number should be greater than 0";
}
return null;
},
onSaved: (value) {
print("onsave numOfPersons $value");
numOfPersons = int.parse(value);
},
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: "Number of persons",
hintText: "5",
border: OutlineInputBorder(),
),
),
Padding(
padding: const EdgeInsets.only(top: 15.0),
child: RaisedButton(
child: Text(
"Calculate",
style: TextStyle(color: Colors.white),
),
onPressed: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
print("calculate $amount");
print("calculate $numOfPersons");
setState(() {
splitAmount = amount / numOfPersons;
});
}
},
),
),
],
),
),
),
);
}
}

It is not flutter bug however when you take amount as integer any value starts with 0, flutter takes it as 0.
if (value.isEmpty) {
return "Enter some amount";
} else if (double.parse(value) <= 0) {
return "Amount should be greater than 0";
}
return null;
and
onChanged: (value) {
amount = double.parse(value);
},

Related

I have problem in dropDownButton in Flutter. Can anyone help me?

here is my code of dropDownButton.I'm trying to making select GenderList dropDownButton but when I assign variable selectedGender to value of dropDownButton it's hide hint text and default select first index value Male.
I want this output
But I'm getting this output when i assign selectedGender variable type int to value in dropDownButton
Here is my code when I comment on the value then it shows a hint text let's see my code below.
import'package:flutter/material.dart';
class DropDownButton extends StatefulWidget {
const DropDownButton({Key? key}) : super(key: key);
#override
State<DropDownButton> createState() => _DropDownButtonState();
}
class _DropDownButtonState extends State<DropDownButton> {
List<DropdownMenuItem<int>> genderList=[];
void loadGenderlist(){
genderList=[];
genderList.add(const DropdownMenuItem(
child: Text('Male'),
value: 0,));
genderList.add(const DropdownMenuItem(
child: Text('Female'),
value: 1,));
genderList.add(const DropdownMenuItem(
child: Text('Other'),
value: 2,));
}
String selectval = "United Kingdom";
int selectedGender = 0;
#override
Widget build(BuildContext context) {
loadGenderlist();
return Scaffold(
appBar: AppBar(
title: Text("Form Validation with All Controls"),
backgroundColor: Colors.redAccent
),
body: SafeArea(
child: ListView(
children: [
DecoratedBox(
decoration: BoxDecoration(
color: Colors.green,
border: Border.all(color: Colors.black38,width: 3)
),
child: DropdownButton(
hint: Text("Select Gender"),
items: genderList,
//value: selectedGender,
onChanged: (value){
setState(() {
selectedGender =int.parse(value.toString()) ;
});
},
underline: Container(),
),
)
],
),
)
);
}
}
I want this output:

How to hide a button on click in a flutter application?

normal image
image after entering the mobile number
See in this image when a user clicks on the login button the login button should disappear and a text field will be there followed by a submit button.
I have the text field appearing on click of the login button, however I don't know how to disappear that login button once it is pressed.
import 'dart:async';
import 'dart:ffi';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../widgets/landing_page.dart';
import '../screens/register.dart';
import 'package:google_sign_in/google_sign_in.dart';
import '../widgets/google_sign_in_btn.dart';
import '../widgets/reactive_refresh_indicator.dart';
// Each item on AuthStatus represents quite literally the status of the UI.
// On SOCIAL_AUTH only the GoogleSignInButton will be visible.
enum AuthStatus { SOCIAL_AUTH }
class AuthScreen extends StatefulWidget {
#override
_AuthScreenState createState() => _AuthScreenState();
}
// On _AuthScreenState we start by defining the tag that will be used for our logger, then the default status as SOCIAL_AUTH, which means we need to do Google's sign in and the GoogleSignInButton will be visible and interactive.
class _AuthScreenState extends State<AuthScreen> {
String phoneNo;
String smsCode;
String verificationId;
bool _smsCodeDisabled = true;
bool isThere;
bool isPresent;
bool _canShowButton = false;
final db = Firestore.instance;
Firestore.instance.collection('transcriber_user_registeration').where('mobileno', isEqualTo: phoneNo)
// .snapshots().listen(
// (data) { print("Inside phone number check : $data"); });
// // return phoneNumberCheck(phoneNo);
// QuerySnapshot result =
// await Firestore.instance.collection('transcriber_user_registeration').getDocuments();
// var list = result.documents;
// print("Before data loop");
// list.forEach((data) => print(data));
// print("After data loop");
Future<void> phoneNumberCheck(String phoneNo) async {
print("Start of the function");
//bool registerState = false;
//bool isPresent = false;
Firestore.instance
.collection("transcriber_user_registeration")
.getDocuments()
.then((QuerySnapshot snapshot) {
snapshot.documents.forEach((f) async {
if (isPresent = ('${f.data['mobileno']}' == phoneNo)) {
print(isPresent);
final PhoneCodeAutoRetrievalTimeout autoRetrieve = (String verId) {
this.verificationId = verId;
};
final PhoneCodeSent smsCodeSent =
(String verId, [int forceCodeResend]) {
this.verificationId = verId;
print("im in sms code dialog");
// smsCodeDialog(context).then((value) {
// print('Signed in');
// });
setState(() {
this._smsCodeDisabled = false;
});
};
final PhoneVerificationCompleted verifySuccess =
(AuthCredential user) {
print("verified");
};
final PhoneVerificationFailed verifyFailed =
(AuthException exception) {
print('${exception.message}');
};
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: this.phoneNo,
codeAutoRetrievalTimeout: autoRetrieve,
codeSent: smsCodeSent,
timeout: const Duration(seconds: 5),
verificationCompleted: verifySuccess,
verificationFailed: verifyFailed,
);
}
// else {
// _showMessage();
// }
});
});
//print("End of the function $isPresent");
}
Future<void> verifyPhone() async {
// final PhoneCodeAutoRetrievalTimeout autoRetrieve = (String verId) {
// this.verificationId = verId;
// };
var tmp1 = phoneNo.toString();
print('ref stsmt starts $tmp1');
await phoneNumberCheck(phoneNo);
print("After execution of the function $isPresent");
print('bvnnn');
}
Future<bool> smsCodeDialog(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return new AlertDialog(
title: Text('Enter sms code'),
content: TextField(onChanged: (value) {
this.smsCode = value;
}),
contentPadding: EdgeInsets.all(10.0),
actions: <Widget>[
new FlatButton(
child: Text('Login'),
onPressed: () async {
//await signIn();
await FirebaseAuth.instance.currentUser().then((user) {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => LandingPageApp()));
});
})
],
);
});
}
signIn() {
print("came to sign in page");
final AuthCredential credential = PhoneAuthProvider.getCredential(
verificationId: verificationId,
smsCode: smsCode,
);
FirebaseAuth.instance.signInWithCredential(credential).then((user) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => LandingPageApp()));
}).catchError((e) {
print(e);
});
}
void hideWidget() {
setState(() {
_canShowButton != _canShowButton;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Container(
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Flexible(
flex: 1,
child: new Container(
//color: Colors.white,
height: 200.0,
width: 400.0,
decoration: new BoxDecoration(
image: DecorationImage(
image: new AssetImage('assets/images/download.png'),
),
),
),
),
SizedBox(height: 20.0),
Padding(
padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 20.0),
child: TextFormField(
decoration:
InputDecoration(hintText: 'Enter your Phone number'),
keyboardType: TextInputType.phone,
onChanged: (value) {
this.phoneNo = "+91$value";
},
validator: validateMobile),
),
SizedBox(height: 10.0),
Visibility(
visible: _canShowButton,
child: RaisedButton(
child: Text('Login'),
textColor: Colors.white,
elevation: 7.0,
color: Colors.blue,
onPressed: () async {
bool _canShowButton = true;
await verifyPhone();
setState(() {
_canShowButton = !_canShowButton;
});
hideWidget();
//_number();
},
),
),
_smsCodeDisabled
? SizedBox(height: 10.0)
: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(
horizontal: 20.0, vertical: 16.0),
child: TextFormField(
decoration:
InputDecoration(hintText: 'Enter OTP'),
keyboardType: TextInputType.number,
onChanged: (value) {
this.smsCode = value;
},
validator: validateOtp),
),
SizedBox(height: 10.0),
RaisedButton(
onPressed: () async {
await signIn();
FirebaseAuth.instance.currentUser().then((user) {
print(["user", user]);
if (user != null) {
print(user.uid);
// Navigator.of(context).pop();
//// Navigator.of(context).pushReplacementNamed('/homePage');
// Navigator.of(context).push(
// MaterialPageRoute<Null>(
// builder: (BuildContext context) {
// return new LandingPageApp();
// }));
} else {
print("user is null");
Navigator.of(context).pop();
signIn();
}
});
},
child: Text('Submit'),
textColor: Colors.white,
elevation: 7.0,
color: Colors.blue,
),
],
),
SizedBox(
height: 20.0,
),
Column(
children: <Widget>[
Row(children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(
vertical: 16.0, horizontal: 20.0),
child: Text(
'Not a Registered User?',
style: TextStyle(
fontSize: 16,
),
),
),
MaterialButton(
child: Text(
'Register',
style: TextStyle(
color: Colors.black,
),
),
//color: Colors.blue,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Register()));
},
),
]),
],
),
],
),
));
}
}
String validateMobile(String value) {
//Indian Mobile numbers are of 10 digits only.
if (value.length != 10)
return 'Mobile number must be of 10 digits';
else
return null;
}
String validateOtp(String value) {
//Otp needs to be of 6 digits
if (value.length != 6)
return 'OTP must be of 6 digits';
else
return null;
}
Please try this
bool _canShowButton = true;
void hideWidget() {
setState(() {
_canShowButton = !_canShowButton;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test Screen'),
),
body: Container(
padding: const EdgeInsets.all(8),
child: Column(
children: <Widget>[
Flexible(
flex: 1,
child: Container(
//color: Colors.white,
height: 200.0,
width: 400.0,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('https://picsum.photos/250?image=10'),
),
),
),
),
SizedBox(height: 20.0),
///if the show button is false
!_canShowButton
? const SizedBox.shrink()
: RaisedButton(
child: Text('Login'),
textColor: Colors.white,
elevation: 7.0,
color: Colors.blue,
onPressed: () {
hideWidget();
//_number();
},
),
],
)),
);
}
In your build method, you already have a boolean _smsCodeDisabled to identify whether the otp field need to show or not. You can use the same boolean to hide the login field and button. The logic should be something like:
#override
Widget build(BuildContext context) {
return _smsCodeDisabled ? _getLoginWidget() : _getOtpWidget();
}
Widget _getLoginWidget() {
// Return Login field and button
}
Widget _getOtpWidget() {
// Return otp field and button
}
Or If you need to only hide the login button, use the following logic:
#override
Widget build(BuildContext context) {
return Scaffold(
// other code
// Login button logic
_smsCodeDisabled ? RaisedButton(...) : Container();
);
}

Allow upto 3 decimal points value in flutter InputTextType.number in flutter

Below is the code I have tried but it completely fails. If I Remove the WhitelistingTextInputFormatter, I get the Number keyboard and I can insert numbers and periods. but the number of period I can use is more than one, which I need to limit at just one. how to do this?
TextField(
controller: _weightCtr,
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
BlacklistingTextInputFormatter(new RegExp('[\\-|\\ ]')),
WhitelistingTextInputFormatter(new RegExp('^\d+[\.\,]\d+\$')),
],
decoration: InputDecoration(
hintText: "Please enter a valid weight for this trip",
),
style: Theme.of(context).textTheme.title.copyWith(
fontWeight: FontWeight.w300,
fontSize: 14,
),
),
You can copy paste run full code below
You can extend TextInputFormatter and remove extra dot
In working demo you can see when extra dot will not show on screen
code snippet
class NumberRemoveExtraDotFormatter extends TextInputFormatter {
NumberRemoveExtraDotFormatter({this.decimalRange = 3})
if (nValue.split('.').length > 2) {
List<String> split = nValue.split('.');
nValue = split[0] + '.' + split[1];
}
...
TextField(
controller: _weightCtr,
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
NumberRemoveExtraDotFormatter()
//BlacklistingTextInputFormatter(new RegExp('[\\-|\\ ]')),
//WhitelistingTextInputFormatter(new RegExp('^\d+[\.\,]\d+\$')),
],
decoration: InputDecoration(
hintText: "Please enter a valid weight for this trip",
),
style: Theme.of(context).textTheme.title.copyWith(
fontWeight: FontWeight.w300,
fontSize: 14,
),
),
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:math' as math;
class NumberRemoveExtraDotFormatter extends TextInputFormatter {
NumberRemoveExtraDotFormatter({this.decimalRange = 3})
: assert(decimalRange == null || decimalRange > 0);
final int decimalRange;
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
String nValue = newValue.text;
TextSelection nSelection = newValue.selection;
Pattern p = RegExp(r'(\d+\.?)|(\.?\d+)|(\.?)');
nValue = p
.allMatches(nValue)
.map<String>((Match match) => match.group(0))
.join();
if (nValue.startsWith('.')) {
nValue = '0.';
} else if (nValue.contains('.')) {
if (nValue.substring(nValue.indexOf('.') + 1).length > decimalRange) {
nValue = oldValue.text;
} else {
if (nValue.split('.').length > 2) {
List<String> split = nValue.split('.');
nValue = split[0] + '.' + split[1];
}
}
}
nSelection = newValue.selection.copyWith(
baseOffset: math.min(nValue.length, nValue.length + 1),
extentOffset: math.min(nValue.length, nValue.length + 1),
);
return TextEditingValue(
text: nValue, selection: nSelection, composing: TextRange.empty);
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
TextEditingController _weightCtr = TextEditingController();
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: _weightCtr,
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
NumberRemoveExtraDotFormatter()
//BlacklistingTextInputFormatter(new RegExp('[\\-|\\ ]')),
//WhitelistingTextInputFormatter(new RegExp('^\d+[\.\,]\d+\$')),
],
decoration: InputDecoration(
hintText: "Please enter a valid weight for this trip",
),
style: Theme.of(context).textTheme.title.copyWith(
fontWeight: FontWeight.w300,
fontSize: 14,
),
),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

validation is becomes true before validation is checked in flutter

I have a question about validation. My widget field validation is become true before checked. when I am just open this page validation is automatically becomes true.
I want that validation after user input. but this validation is becoming true before the user entering something inside from the field. so can anyone help me? your help will be appreciated.
Here is the code I've tried.
class BspSignupPage extends StatefulWidget {
static const String routeName = "/bspSignup";
#override
_BspSignupPageState createState() => _BspSignupPageState();
}
class _BspSignupPageState extends State<BspSignupPage>
with AfterLayoutMixin<BspSignupPage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
// final TextEditingController _bspPhone = TextEditingController();
final MaskedTextController _bspPhone =
new MaskedTextController(mask: '(000)-000-0000');
final TextEditingController _bspBusinessName = TextEditingController();
final TextEditingController _bspBusinessLegalAddress =
TextEditingController();
final TextEditingController _bspBusinessLicense = TextEditingController();
final TextEditingController _bspLicenseAuthority = TextEditingController();
final TextEditingController _bspEstYear = TextEditingController();
final TextEditingController _bspNumberOfEmployee = TextEditingController();
final TextEditingController _bspBusinessDetailsComment =
TextEditingController();
final TextEditingController _countryCodeController =
new TextEditingController();
BSPSignupRepository _bspSignupRepository = new BSPSignupRepository();
bool bspcheck = false;
BspSignupCommonModel model = BspSignupCommonModel();
int radioValue = -1;
String _alternatephone;
String _businessname;
bool addressenabled = false;
List<dynamic> _type = <dynamic>[];
Map<String, dynamic> _typeValue;
String _establishyear;
String _numberofemployee;
LocationResult _pickedLocation;
DateTime selectedDate = DateTime.now();
bool flexibletime = false;
DateTime date;
TimeOfDay time;
Map<String, dynamic> bspsignupdata = new Map<String, dynamic>();
#override
void initState() {
super.initState();
print(model);
_bspNumberOfEmployee.text = "1";
_bspSignupRepository.getBSTypes().then((businessTypeResponse) {
print('businessTypeResponse');
print(businessTypeResponse);
if (businessTypeResponse['error'] != null) {
} else {
setState(() {
_type = businessTypeResponse['data']['businessTypes'];
});
}
});
setState(() {
date = new DateTime.now().add(new Duration(hours: 1));
time = new TimeOfDay.fromDateTime(date);
});
}
#override
void afterFirstLayout(BuildContext context) {
model = ModalRoute.of(context).settings.arguments;
if (model == null) {
model = new BspSignupCommonModel();
} else {
print('model for edit');
_setExistingDetails(model);
}
}
void _setExistingDetails(bspModel) {
_bspBusinessName.text = bspModel.businessLegalName;
_bspPhone.text = model.businessPhoneNumber;
_bspEstYear.text = model.businessYear;
_bspNumberOfEmployee.text = model.numberofEmployees;
_bspBusinessLegalAddress.text = model.businessLegalAddress;
_typeValue = model.businessTypes;
}
Widget _buildlegalbusinessname() {
return new TudoTextWidget(
controller: _bspBusinessName,
textCapitalization: TextCapitalization.sentences,
prefixIcon: Icon(Icons.business),
labelText: AppConstantsValue.appConst['bspSignup']['legalbusinessname']
['translation'],
hintText: AppConstantsValue.appConst['bspSignup']['legalbusinessname']
['translation'],
validator: (val) =>
Validators.validateRequired(val, "Business legal name"),
onSaved: (val) {
_businessname = val;
bspsignupdata['businessname'] = _businessname;
},
);
}
Widget _buildalternatephone() {
return Row(
children: <Widget>[
new Expanded(
child: new TudoTextWidget(
controller: _countryCodeController,
enabled: false,
prefixIcon: Icon(FontAwesomeIcons.globe),
labelText: "code",
hintText: "Country Code",
),
flex: 2,
),
new SizedBox(
width: 10.0,
),
new Expanded(
child: new TudoNumberWidget(
controller: _bspPhone,
validator: Validators().validateMobile,
labelText: AppConstantsValue.appConst['bspSignup']['alternatephone']
['translation'],
hintText: AppConstantsValue.appConst['bspSignup']['alternatephone']
['translation'],
prefixIcon: Icon(Icons.phone),
onSaved: (val) {
_alternatephone = val;
bspsignupdata['alternatephone'] = _alternatephone;
},
),
flex: 5,
),
],
);
}
Widget _buildestablishedyear() {
return new TudoNumberWidget(
controller: _bspEstYear,
prefixIcon: Icon(FontAwesomeIcons.calendar),
labelText: AppConstantsValue.appConst['bspSignup']['establishedyear']
['translation'],
hintText: AppConstantsValue.appConst['bspSignup']['establishedyear']
['translation'],
validator: Validators().validateestablishedyear,
maxLength: 4,
onSaved: (val) {
_establishyear = val.trim();
bspsignupdata['establishyear'] = _establishyear;
},
);
}
Widget _buildnumberofemployees() {
return new TudoNumberWidget(
controller: _bspNumberOfEmployee,
prefixIcon: Icon(Icons.control_point_duplicate),
labelText: AppConstantsValue.appConst['bspSignup']['numberofemployees']
['translation'],
hintText: AppConstantsValue.appConst['bspSignup']['numberofemployees']
['translation'],
validator: Validators().validatenumberofemployee,
onSaved: (val) {
_numberofemployee = val.trim();
bspsignupdata['numberofemployes'] = _numberofemployee;
},
);
}
Widget _buildbusinesslegaladdress() {
return Row(
children: <Widget>[
new Expanded(
child: new TudoTextWidget(
prefixIcon: Icon(Icons.business),
labelText: AppConstantsValue.appConst['bspSignup']
['businesslegaladdress']['translation'],
hintText: AppConstantsValue.appConst['bspSignup']
['businesslegaladdress']['translation'],
controller: _bspBusinessLegalAddress,
enabled: addressenabled,
validator: (val) =>
Validators.validateRequired(val, "Business legal name"),
),
flex: 5,
),
new SizedBox(
width: 10.0,
),
new Expanded(
child: new FloatingActionButton(
backgroundColor: colorStyles['primary'],
child: Icon(
FontAwesomeIcons.globe,
color: Colors.white,
),
elevation: 0,
onPressed: () async {
LocationResult result = await LocationPicker.pickLocation(
context,
"AIzaSyDZZeGlIGUIPs4o8ahJE_yq6pJv3GhbKQ8",
);
print("result = $result");
setState(() {
_pickedLocation = result;
addressenabled = !addressenabled;
});
// setState(() => _pickedLocation = result);
_bspBusinessLegalAddress.text = _pickedLocation.address;
model.businessGeoLocation = new BusinessGeoLocation(
lat: _pickedLocation.latLng.latitude.toString(),
lng: _pickedLocation.latLng.longitude.toString(),
);
},
),
flex: 2,
),
],
);
}
Widget _buildbusinesstype() {
return FormBuilder(
autovalidate: true,
child: FormBuilderCustomField(
attribute: "Business type",
validators: [FormBuilderValidators.required()],
formField: FormField(
builder: (FormFieldState<dynamic> field) {
return InputDecorator(
decoration: InputDecoration(
prefixIcon: Icon(Icons.perm_identity),
labelText: _type == []
? 'Select Personal Identification type'
: 'Business type',
hintText: "Select Personal Identification type",
errorText: field.errorText,
),
isEmpty: _typeValue == [],
child: new DropdownButtonHideUnderline(
child: new DropdownButton(
// isExpanded: true,
hint: Text("Select Personal Identification type"),
value: _typeValue,
isDense: true,
onChanged: (dynamic newValue) {
print('newValue');
print(newValue);
setState(() {
_typeValue = newValue;
field.didChange(newValue);
});
},
items: _type.map(
(dynamic value) {
return new DropdownMenuItem(
value: value,
child: new Text(value['name']),
);
},
).toList(),
),
),
);
},
)),
);
}
Widget _buildlegalbusinesscheck() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bspSignup']['legalbusinesscheck']
['translation'],
errortext: AppConstantsValue.appConst['bspSignup']['errortext']
['translation'],
);
}
Widget content(BuildContext context, BspSignupViewModel bspSignupVm) {
final appBar = AppBar(
title: Text("BSP Signup"),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
NavigationHelper.navigatetoBack(context);
},
),
centerTitle: true,
);
final bottomNavigationBar = Container(
color: Colors.transparent,
height: 56,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FlatButton.icon(
icon: Icon(Icons.close),
label: Text('Clear'),
color: Colors.redAccent,
textColor: Colors.black,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
_formKey.currentState.reset();
_bspPhone.clear();
_bspBusinessName.clear();
_bspBusinessLicense.clear();
_bspLicenseAuthority.clear();
_bspEstYear.clear();
_bspNumberOfEmployee.clear();
_bspBusinessDetailsComment.clear();
_bspBusinessLegalAddress.clear();
},
),
new FlatButton.icon(
icon: Icon(FontAwesomeIcons.arrowCircleRight),
label: Text('Next'),
color: colorStyles["primary"],
textColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () async {
if (_formKey.currentState.validate()) {
model.businessLegalName = _bspBusinessName.text;
model.businessPhoneNumber = _bspPhone.text;
model.businessYear = _bspEstYear.text;
model.numberofEmployees = _bspNumberOfEmployee.text;
model.businessType = _typeValue['id'];
model.businessLegalAddress = _bspBusinessLegalAddress.text;
model.businessTypes = _typeValue;
print('model');
print(model.licensed);
if (_typeValue['name'].toLowerCase() ==
"Licensed / Registered".toLowerCase()) {
model.isLicensed = true;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BspLicensedSignupPage(
bspSignupCommonModel: model,
),
),
);
} else {
model.isLicensed = false;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BspUnlicensedSignupPage(
bspSignupCommonModel: model,
),
),
);
}
}
},
),
],
),
);
return Scaffold(
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: Container(
height: double.infinity,
width: double.infinity,
child: Form(
autovalidate: true,
key: _formKey,
child: Stack(
children: <Widget>[
// Background(),
SingleChildScrollView(
padding: const EdgeInsets.all(30.0),
child: new Container(
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildlegalbusinessname(),
_buildalternatephone(),
_buildestablishedyear(),
_buildnumberofemployees(),
SizedBox(
height: 5,
),
_buildbusinesslegaladdress(),
_buildbusinesstype(),
_buildlegalbusinesscheck(),
],
),
),
),
],
),
),
),
);
}
#override
Widget build(BuildContext context) {
return new StoreConnector<AppState, BspSignupViewModel>(
converter: (Store<AppState> store) => BspSignupViewModel.fromStore(store),
onInit: (Store<AppState> store) {
_countryCodeController.text =
store.state.auth.loginUser.user.country.isdCode;
},
builder: (BuildContext context, BspSignupViewModel bspSignupVm) =>
content(context, bspSignupVm),
);
}
}
your widget field validation is become true before checked becuase you have given static flag true to "autovalidate" to solve this issue you have to manage flag variable for that
Example:-
bool _autoValidate = false;
Form(
key: _formKey,
autovalidate: _autoValidate,
child: Container(child:Text("")));
And change flag value when first time validating form
void _buttonClicked(BuildContext context) {
setState(() {
_autoValidate = true;
});
}
Update:-
autovalidate is deprecated from Flutter v1.19
Replace autovalidate with autovalidateMode.autovalidateMode can have one of the below 3 values:
autovalidateMode: AutovalidateMode.disabled: No auto validation will occur.
autovalidateMode: AutovalidateMode.always: Used to auto-validate FormField even without user interaction.
autovalidateMode: AutovalidateMode.onUserInteraction: Used to auto-validate FormField only after each user interaction.

Flutter ListView.builder - How to Jump to Certain Index Programmatically

i have a screen that build using MaterialApp, DefaultTabController, Scaffold and TabBarView.
in this screen, i have body content that retreive a list of element from sqllite using StreamBuilder. i get exact 100 elements ("finite list") to be shown using ListView.
my question, using ListView.builder, How we can jump to certain index when this screen opened ?
my main screen:
...
ScrollController controller = ScrollController();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner : false,
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
backgroundColor: Pigment.fromString(UIData.primaryColor),
elevation: 0,
centerTitle: true,
title: Text(translations.text("quran").toUpperCase()),
bottom: TabBar(
tabs: [
Text("Tab1"),
Text("Tab2"),
Text("Tab3")
],
),
leading: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: InkWell(
child: SizedBox(child: Image.asset("assets/images/home.png"), height: 10, width: 1,),
onTap: () => Navigator.of(context).pop(),
)
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _scrollToIndex,
tooltip: 'Testing Index Jump',
child: Text("GO"),
),
body:
TabBarView(
children: [
Stack(
children: <Widget>[
MyDraggableScrollBar.create(
scrollController: controller,
context: context,
heightScrollThumb: 25,
child: ListView(
controller: controller,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(30, 15, 30, 8),
child: Container(
alignment: Alignment.center,
height: 30,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: TextField(
style: TextStyle(color: Colors.green),
decoration: new InputDecoration(
contentPadding: EdgeInsets.all(5),
border: InputBorder.none,
filled: true,
hintStyle: new TextStyle(color: Colors.green, fontSize: 14),
prefixIcon: Icon(FontAwesomeIcons.search,color: Colors.green,size: 17,),
hintText: translations.text("search-quran"),
fillColor: Colors.grey[300],
prefixStyle: TextStyle(color: Colors.green)
),
onChanged: (val) => quranBloc.searchSurah(val),
),
)
)
),
//surah list
streamBuilderQuranSurah(context)
],
)
) // MyDraggableScrollBar
],
),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
)
)));
}
Widget streamBuilderQuranSurah(BuildContext ctx){
return StreamBuilder(
stream: quranBloc.chapterStream ,
builder: (BuildContext context, AsyncSnapshot<ChaptersModel> snapshot){
if(snapshot.hasData){
return ListView.builder(
controller: controller,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount:(snapshot.data.chapters?.length ?? 0),
itemBuilder: (BuildContext context, int index) {
var chapter =
snapshot.data.chapters?.elementAt(index);
return chapterDataCell(chapter);
},
);
}
else{
return SurahItemShimmer();
}
},
);
}
...
class MyDraggableScrollBar.dart :
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/material.dart';
class MyDraggableScrollBar {
static Widget create({
#required BuildContext context,
#required ScrollController scrollController,
#required double heightScrollThumb,
#required Widget child,
}) {
return DraggableScrollbar(
alwaysVisibleScrollThumb: true,
scrollbarTimeToFade: Duration(seconds: 3),
controller: scrollController,
heightScrollThumb: heightScrollThumb,
backgroundColor: Colors.green,
scrollThumbBuilder: (
Color backgroundColor,
Animation<double> thumbAnimation,
Animation<double> labelAnimation,
double height, {
Text labelText,
BoxConstraints labelConstraints,
}) {
return InkWell(
onTap: () {},
child: Container(
height: height,
width: 7,
color: backgroundColor,
),
);
},
child: child,
);
}
}
i have tried find other solutions but seems not working, for example indexed_list_view that only support infinite list
and it seems flutter still not have feature for this, see this issue
Any Idea ?
You can use https://pub.dev/packages/scrollable_positioned_list. You can pass the initial index to the widget.
ScrollablePositionedList.builder(
initialScrollIndex: 12, //you can pass the desired index here//
itemCount: 500,
itemBuilder: (context, index) => Text('Item $index'),
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
);
General Solution:
To store anything which can be represented as a number/string/list of strings, Flutter provides a powerful easy-to-use plugin which stores the values needed to be stored along with a key. So the next time you need you'll need to retrieve or even update that value all that you'll need is that key.
To get started, add the shared_preferences plugin to the pubspec.yaml file,
dependencies:
flutter:
sdk: flutter
shared_preferences: "<newest version>"
Run flutter pub get from the terminal or if your using IntelliJ just click on Packages get(You'll find it somewhere around the top-right corner of your screen while viewing the pubspec.yaml file)
Once the above command is successfully executed, import the below file in your main.dart or concerned file.
import 'package:shared_preferences/shared_preferences.dart';
Now just attach a ScrollController to your ListView.builder() widget and make sure that the final/last offset is stored along with a specific key using shared_preferences whenever the user leaves the app in any way and is set when the initState of your concerned widget is called.
In order to know to detect changes in the state of our app and to act with accordance to it, we'll be inheriting WidgetsBindingObserver to our class.
Steps to follow:
Extend the WidgetsBindingObserver class along with the State class of your StatefulWidget.
Define a async function resumeController() as a function member of the above class.
Future<void> resumeController() async{
_sharedPreferences = await SharedPreferences.getInstance().then((_sharedPreferences){
if(_sharedPreferences.getKeys().contains("scroll-offset-0")) _scrollController= ScrollController(initialScrollOffset:_sharedPreferences.getDouble("scroll-offset-0"));
else _sharedPreferences.setDouble("scroll-offset-0", 0);
setState((){});
return _sharedPreferences;
});
Declare two variables one to store and pass the scrollcontroller and the other to store and use the instance of SharedPreferences.
ScrollController _scrollController;
SharedPreferences _sharedPreferences;
Call resumeController() and pass your class to the addObserver method of the instance object in WidgetsBinding class.
resumeController();
WidgetsBinding.instance.addObserver(this);
Simply paste this code in the class definition (outside other member functions)
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_scrollController.dispose();
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if(state==AppLifecycleState.paused || state==AppLifecycleState.inactive || state==AppLifecycleState.suspending)
_sharedPreferences.setDouble("scroll-offset-0", _scrollController.offset);
super.didChangeAppLifecycleState(state);
}
Pass the ScrollController() to the concerned Scrollable.
Working Example:
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> with WidgetsBindingObserver{
//[...]
ScrollController _scrollController;
SharedPreferences _sharedPreferences;
Future<void> resumeController() async{
_sharedPreferences = await SharedPreferences.getInstance().then((_sharedPreferences){
if(_sharedPreferences.getKeys().contains("scroll-offset-0")) _scrollController= ScrollController(initialScrollOffset:_sharedPreferences.getDouble("scroll-offset-0"));
else _sharedPreferences.setDouble("scroll-offset-0", 0);
setState((){});
return _sharedPreferences;
});
}
#override
void initState() {
resumeController();
WidgetsBinding.instance.addObserver(this);
super.initState();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_scrollController.dispose();
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if(state==AppLifecycleState.paused || state==AppLifecycleState.inactive || state==AppLifecycleState.suspending)
_sharedPreferences.setDouble("scroll-offset-0", _scrollController.offset);
super.didChangeAppLifecycleState(state);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("Smart Scroll View"),
),
body: ListView.builder(
itemCount: 50,
controller: _scrollController,
itemBuilder: (c,i)=>
Padding(
padding: EdgeInsets.symmetric(horizontal: 24,vertical: 16),
child: Text((i+1).toString()),
),
),
),
);
}
}
Solution without knowing the size of your widgets
the Solution I found without knowing the size of your widget is displaying a reverse 'sublist' from the index to the end, then scroll to the top of your 'sublist' and reset the entire list. As it is a reverse list the item will be add at the top of the list and you will stay at your position (the index).
the problem is that you can't use a listView.builder because you will need to change the size of the list
example
class _ListViewIndexState extends State<ListViewIndex> {
ScrollController _scrollController;
List<Widget> _displayedList;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_displayedList = widget.items.sublist(0, widget.items.length - widget.index);
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((_) {
//here the sublist is already build
completeList();
});
}
}
completeList() {
//to go to the last item(in first position)
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
//reset the list to the full list
setState(() {
_displayedList = widget.items;
});
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
ListView(
controller: _scrollController,
reverse: true,
children: _displayedList,
),
]
);
}
}
The https://pub.dev/packages/indexed_list_view package could maybe help you out for this. Use something like this:
IndexedListView.builder(
controller: indexScrollController,
itemBuilder: itemBuilder
);
indexScrollController.jumpToIndex(10000);
I'll present another approach, which supports list lazy loading unlike #Shinbly 's method, and also support tiles in list to resize without recalculating the correct offset of the ListView nor saving any persistent information like "#Nephew of Stackoverflow" does.
The essential key to this approach is to utilize CustomScrollView, the CustomScrollView.center property.
Here's an example based on the example code from Flutter document (widgets.CustomScrollView.2):
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
List<int> top = [];
List<int> bottom = [0];
List<int> test = List.generate(10, (i) => -5 + i);
bool positionSwitcher = true;
#override
Widget build(BuildContext context) {
positionSwitcher = !positionSwitcher;
final jumpIndex = positionSwitcher ? 1 : 9;
Key centerKey = ValueKey('bottom-sliver-list');
return Scaffold(
appBar: AppBar(
title: const Text('Press Jump!! to jump between'),
leading: IconButton(
icon: const Icon(Icons.add),
onPressed: () {
setState(() {
top.add(-top.length - 1);
bottom.add(bottom.length);
});
},
),
),
body: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RaisedButton(
child: Text('Jump!!'),
onPressed: () => setState(() {}),
),
Text(positionSwitcher ? 'At top' : 'At bottom'),
],
),
Expanded(
child: CustomScrollView(
center: centerKey,
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) {
final index = jumpIndex - 1 - i;
return Container(
alignment: Alignment.center,
color: Colors.blue[200 + test[index] % 4 * 100],
height: 100 + test[index] % 4 * 20.0,
child: Text('Item: ${test[index]}'),
);
},
childCount: jumpIndex,
),
),
SliverList(
key: centerKey,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int i) {
final index = i + jumpIndex;
return Container(
alignment: Alignment.center,
color: i == 0
? Colors.red
: Colors.blue[200 + test[index] % 4 * 100],
height: 100 + test[index] % 4 * 20.0,
child: Text('Item: ${test[index]}'),
);
},
childCount: test.length - jumpIndex,
),
),
],
),
)
],
),
);
}
}
Explanation:
We use single list as data source for both SliverList
During each rebuild, we use center key to reposition the second SliverList inside ViewPort
Carefully manage the conversion from SliverList index to data source list index
Notice how the scroll view build the first SliverList by passing an index starting from bottom of this SliverList (i.e. index 0 suggests last item in the first list sliver)
Give the CustomeScrollView a proper key to decide whether to "re-position" or not
Working Example:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
class ScrollToIndexDemo extends StatefulWidget {
const ScrollToIndexDemo({Key? key}) : super(key: key);
#override
_ScrollToIndexDemoState createState() => _ScrollToIndexDemoState();
}
class _ScrollToIndexDemoState extends State<ScrollToIndexDemo> {
late AutoScrollController controller = AutoScrollController();
var rng = Random();
ValueNotifier<int> scrollIndex = ValueNotifier(0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: ValueListenableBuilder(
valueListenable: scrollIndex,
builder: (context, index, child) {
return Text('Scroll Demo - $index');
},
),
),
body: ListView.builder(
itemCount: 100,
controller: controller,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.all(8),
child: AutoScrollTag(
key: ValueKey(index),
controller: controller,
index: index,
highlightColor: Colors.black.withOpacity(0.1),
child: Container(
padding: EdgeInsets.all(10),
alignment: Alignment.center,
color: Colors.grey[300],
height: 100,
child: Text(
'index: $index',
style: TextStyle(color: Colors.black),
),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
scrollIndex.value = rng.nextInt(100);
await controller.scrollToIndex(scrollIndex.value, preferPosition: AutoScrollPosition.begin);
},
tooltip: 'Increment',
child: Center(
child: Text(
'Next',
textAlign: TextAlign.center,
),
),
),
);
}
}
You can use the flutter_scrollview_observer lib to implement your desired functionality without invasivity
Create and use instance of ScrollController normally.
ScrollController scrollController = ScrollController();
ListView _buildListView() {
return ListView.builder(
controller: scrollController,
...
);
}
Create an instance of ListObserverController pass it to ListViewObserver
ListObserverController observerController = ListObserverController(controller: scrollController);
ListViewObserver(
controller: observerController,
child: _buildListView(),
...
)
Now you can scroll to the specified index position
// Jump to the specified index position without animation.
observerController.jumpTo(index: 1)
// Jump to the specified index position with animation.
observerController.animateTo(
index: 1,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);

Resources