Flutter provider, Right way to use GlobalKey<FormState> in Provider - flutter

I'm new at Provider package. and Just making demo app for learning purpose.
Here is my code of simple Form Widget.
1) RegistrationPage (Where my app is start)
class RegistrationPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("Title"),
),
body: MultiProvider(providers: [
ChangeNotifierProvider<UserProfileProvider>.value(value: UserProfileProvider()),
ChangeNotifierProvider<RegiFormProvider>.value(value: RegiFormProvider()),
], child: AllRegistrationWidgets()),
);
}
}
class AllRegistrationWidgets extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SetProfilePicWidget(),
RegistrationForm(),
],
),
),
),
BottomSaveButtonWidget()
],
),
),
);
}
}
class BottomSaveButtonWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final _userPicProvider =
Provider.of<UserProfileProvider>(context, listen: false);
final _formProvider =
Provider.of<RegiFormProvider>(context, listen: false);
return SafeArea(
bottom: true,
child: Container(
margin: EdgeInsets.all(15),
child: FloatingActionButton.extended(
heroTag: 'saveform',
icon: null,
label: Text('SUBMIT',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
)),
onPressed: () {
print(_userPicProvider.strImageFileName);
_formProvider.globalFormKey.currentState.validate();
print(_formProvider.firstName);
print(_formProvider.lastName);
},
)),
);
}
}
2) RegistrationForm
class RegistrationForm extends StatefulWidget {
#override
_RegistrationFormState createState() => _RegistrationFormState();
}
class _RegistrationFormState extends State<RegistrationForm> {
TextEditingController _editingControllerFname;
TextEditingController _editingControllerLname;
#override
void initState() {
_editingControllerFname = TextEditingController();
_editingControllerLname = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
final formProvider = Provider.of<RegiFormProvider>(context);
return _setupOtherWidget(formProvider);
}
_setupOtherWidget(RegiFormProvider _formProvider) {
return Container(
padding: EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SizedBox(height: 20),
Text(
'Fields with (*) are required.',
style: TextStyle(fontStyle: FontStyle.italic),
textAlign: TextAlign.left,
),
SizedBox(height: 20),
_formSetup(_formProvider)
],
),
);
}
_formSetup(RegiFormProvider _formProvider) {
return Form(
key: _formProvider.globalFormKey,
child: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: _editingControllerFname,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
labelText: "First Name *",
hintText: "First Name *",
),
onSaved: (value) {},
validator: (String value) =>
_formProvider.validateFirstName(value)),
SizedBox(height: 15),
TextFormField(
controller: _editingControllerLname,
textCapitalization: TextCapitalization.sentences,
validator: (String value) =>
_formProvider.validateLastName(value),
onSaved: (value) {},
decoration: InputDecoration(
labelText: "Last Name *",
hintText: "Last Name *",
),
)
],
),
),
);
}
#override
void dispose() {
_editingControllerFname.dispose();
_editingControllerLname.dispose();
super.dispose();
}
}
3) RegiFormProvider
class RegiFormProvider with ChangeNotifier {
final GlobalKey<FormState> globalFormKey = GlobalKey<FormState>();
String _strFirstName;
String _strLasttName;
String get firstName => _strFirstName;
String get lastName => _strLasttName;
String validateFirstName(String value) {
if (value.trim().length == 0)
return 'Please enter first name';
else {
_strFirstName = value;
return null;
}
}
String validateLastName(String value) {
if (value.trim().length == 0)
return 'Please enter last name';
else {
_strLasttName = value;
return null;
}
}
}
Here you can see, RegiFormProvider is my first page where other is children widgets in widget tree. I'm using final GlobalKey<FormState> globalFormKey = GlobalKey<FormState>(); in the RegiFormProvider provider, Because I want to access this in the 1st RegistrationPage to check my firstName and lastName is valid or not.

I'm using a builder widget to get form level context like below , and then easily we can get the form instance by using that context. by this way we don't need global key anymore.
Form(
child: Builder(
builder: (ctx) {
return ListView(
padding: EdgeInsets.all(12),
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: "Title"),
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
initialValue: formProduct.title,
validator: validateTitle,
onSaved: (value) {
formProduct.title = value;
},
),
TextFormField(
decoration: InputDecoration(labelText: "Price"),
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
initialValue: formProduct.price == null
? ""
: formProduct.price.toString(),
keyboardType: TextInputType.number,
validator: validatePrice,
onSaved: (value) {
formProduct.price = double.parse(value);
},
),
TextFormField(
decoration: InputDecoration(labelText: "Description"),
textInputAction: TextInputAction.next,
initialValue: formProduct.description,
maxLines: 3,
validator: validateDescription,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
onSaved: (value) {
formProduct.description = value;
},
),
TextFormField(
decoration: InputDecoration(labelText: "Image Url"),
textInputAction: TextInputAction.done,
onFieldSubmitted: (_) => FocusScope.of(context).unfocus(),
initialValue: formProduct.imageUrl,
validator: validateImageUrl,
onSaved: (value) {
formProduct.imageUrl = value;
},
),
Padding(
padding: EdgeInsets.all(10),
child: FlatButton(
color: Colors.amberAccent,
onPressed: () {
if (Form.of(ctx).validate()) {
Form.of(ctx).save();
formProduct.id =
Random.secure().nextDouble().toString();
ProductsProvider provider =
Provider.of<ProductsProvider>(context,
listen: false);
editing
? provider.setProduct(formProduct)
: provider.addProduct(formProduct);
Router.back(context);
}
},
child: Text("Save"),
),
)
],
);
},
),
)
you can see the Form.of(ctx) gives us the current level form.

Related

How to validate the TextFormField as we type in the input in Flutter

I have created a login screen with textformfield for email id and password using flutter. Also, I have added the validation to check these fields. The code is as below;
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
theme: ThemeData(
brightness: Brightness.dark,
),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
var _formKey = GlobalKey<FormState>();
var isLoading = false;
void _submit() {
final isValid = _formKey.currentState.validate();
if (!isValid) {
return;
}
_formKey.currentState.save();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Form Validation"),
leading: Icon(Icons.filter_vintage),
),
//body
body: Padding(
padding: const EdgeInsets.all(16.0),
//form
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
Text(
"Form-Validation In Flutter ",
style: TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold),
),
//styling
SizedBox(
height: MediaQuery.of(context).size.width * 0.1,
),
TextFormField(
decoration: InputDecoration(labelText: 'E-Mail'),
keyboardType: TextInputType.emailAddress,
onFieldSubmitted: (value) {
//Validator
},
validator: (value) {
if (value.isEmpty ||
!RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+#[a-zA-Z0-9]+\.[a-zA-Z]+")
.hasMatch(value)) {
return 'Enter a valid email!';
}
return null;
},
),
//box styling
SizedBox(
height: MediaQuery.of(context).size.width * 0.1,
),
//text input
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
keyboardType: TextInputType.emailAddress,
onFieldSubmitted: (value) {},
obscureText: true,
validator: (value) {
if (value.isEmpty) {
return 'Enter a valid password!';
}
return null;
},
),
SizedBox(
height: MediaQuery.of(context).size.width * 0.1,
),
RaisedButton(
padding: EdgeInsets.symmetric(
vertical: 10.0,
horizontal: 15.0,
),
child: Text(
"Submit",
style: TextStyle(
fontSize: 24.0,
),
),
onPressed: () => _submit(),
)
],
),
),
),
);
}
}
The issue I am facing is, I want to validate the fields as soon as the user starts typing the input(dynamically) rather than clicking on the submit button to wait for the validation to happen. I did a lot of research yet could not find a solution. Thanks in advance for any help!
Flutter Form Validation with TextFormField
Here's an alternative implementation of the _TextSubmitWidgetState that uses a Form:
class _TextSubmitWidgetState extends State<TextSubmitForm> {
// declare a GlobalKey
final _formKey = GlobalKey<FormState>();
// declare a variable to keep track of the input text
String _name = '';
void _submit() {
// validate all the form fields
if (_formKey.currentState!.validate()) {
// on success, notify the parent widget
widget.onSubmit(_name);
}
}
#override
Widget build(BuildContext context) {
// build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
decoration: const InputDecoration(
labelText: 'Enter your name',
),
// use the validator to return an error string (or null) based on the input text
validator: (text) {
if (text == null || text.isEmpty) {
return 'Can\'t be empty';
}
if (text.length < 4) {
return 'Too short';
}
return null;
},
// update the state variable when the text changes
onChanged: (text) => setState(() => _name = text),
),
ElevatedButton(
// only enable the button if the text is not empty
onPressed: _name.isNotEmpty ? _submit : null,
child: Text(
'Submit',
style: Theme.of(context).textTheme.headline6,
),
),
],
),
);
}
}
source : https://codewithandrea.com/articles/flutter-text-field-form-validation/
May be this can help someone. Inside the TextFormField use this line of code:
autovalidateMode: AutovalidateMode.onUserInteraction
use autovalidateMode in your Form widget
Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: FormUI(),
),

how to make a button disable in flutter

I'am trying to create a profile form . I want to disable or enable the button based on the validation. I tried to check the currentstate of form validated or not. it is working fine but when i type on one of the textFormField , I am getting error message on all of the textFormField .
Here is the code i'am working.
import 'dart:html';
import 'package:flutter/material.dart';
class ProfileForm extends StatefulWidget {
const ProfileForm({Key? key}) : super(key: key);
#override
State<ProfileForm> createState() => _ProfileFormState();
}
class _ProfileFormState extends State<ProfileForm> {
final _formKey = GlobalKey<FormState>();
DateTime selectedDate = DateTime.now();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _dobController = TextEditingController();
final _currentLocationController = TextEditingController();
final _locationIndiaController = TextEditingController();
bool btndisable = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
RegExp emailValidator = RegExp(
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,}))$');
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
onChanged: () {
notdisbled();
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Let’s create a profile for you",
style: AppTextStyleSecondary.headline2sec,
),
const SizedBox(
height: 12,
),
Padding(
padding: const EdgeInsets.only(right: 100.0),
child: Text(
"Please enter your personal details",
),
),
const SizedBox(
height: 16,
),
Text("Your name"),
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
onChanged: (val) {
},
validator: (val) {
if (val.toString().length < 2) {
return "Please enter your name";
}
return null;
},
controller: _nameController,
decoration: const InputDecoration(
isDense: true,
contentPadding:
EdgeInsets.symmetric(horizontal: 10, vertical: 12)),
),
Text(labelText: "Date of Birth"),
InkWell(
child: TextFormField(
onTap: () {
_selectDate(context);
},
validator: (val) {
if (val.toString().isEmpty) {
return "This field is required";
} else {
return null;
}
},
readOnly: true,
controller: _dobController,
),
),
Text("Email address"),
TextFormField(
controller: _emailController,
onChanged: (val) {},
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (val) {
String email = val!.trim();
if (emailValidator.hasMatch(email)) {
return null;
} else {
return "Enter valid email address";
}
},
decoration: const InputDecoration(
isDense: true,
contentPadding:
EdgeInsets.symmetric(horizontal: 10, vertical: 12)),
),
Text(labelText: "Your location where you currently live"),
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (val) {
if (val.toString().isEmpty) {
return "This field is required";
} else {
return null;
}
},
onChanged: (val) {
},
controller: _currentLocationController,
),
Text("Your location in India"),
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (val) {
if (val.toString().isEmpty) {
return "This field is required";
} else {
return null;
}
},
onChanged: (val) {},
controller: _locationIndiaController,
),
const SizedBox(
height: 25,
),
Container(
width: double.infinity,
height: 45,
decoration: BoxDecoration(
color: btndisable ? Colors.blue : Colors.red,
borderRadius: BorderRadius.circular(8),
),
child: TextButton(
onPressed: (){
if(_formKey.currentState!.validate()){
Navigator.of(context).pushNamedAndRemoveUntil(
'HomeScreen', (Route<dynamic> route) => false);
}
}
child: Text("Continue"),
),
),
const SizedBox(
height: 20,
),
],
),
),
)),
);
}
Future<void> _selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(1970),
lastDate: DateTime(2101));
if (picked != null && picked != selectedDate) {
setState(() {
selectedDate = picked;
_dobController.text =
"${selectedDate.day}/${selectedDate.month}/${selectedDate.year}";
});
}
}
void notdisbled() {
if (_formKey.currentState!.validate() == false) {
setState(() {
btndisable = false;
});
} else {
setState(() {
btndisable = true;
});
}
}
}
for disable the button change to this :
child: TextButton(
onPressed: _formKey.currentState != null && _formKey.currentState.validate() ? () {
Navigator.of(context).pushNamedAndRemoveUntil(
'HomeScreen', (Route<dynamic> route) => false);
}: null,
child: AppText(
text: "Continue",
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColor.white),
),
but when you use
formKey.currentState!.validate()
when you type any thing, flutter try call it every time so your form widget run all validator func in all its textfield. so it is natural for all textfield to get error message

Flutter TextFormField Cursor Reset To First position of letter after onChanged

I need some help regarding flutter textformfield This is my code for the textfield controller. The problem is when I type new word,the cursor position is moved automatically from right to left (reset)(before first letter inside box). How I can make the cursor work as usual at the end of current text. I have read few solutions from stack overflow but it still not working. Please help me. Thanks.
class BillingWidget extends StatelessWidget {
final int pageIndex;
final Function validateController;
final formKey = new GlobalKey<FormState>();
BillingWidget(this.billingDetails,this.pageIndex,this.validateController);
final BillingDetails billingDetails;
#override
Widget build(BuildContext context) {
return Form(
key: formKey,
onChanged: () {
if (formKey.currentState.validate()) {
validateController(pageIndex,false);
formKey.currentState.save();
final val = TextSelection.collapsed(offset: _textTEC.text.length);
_textTEC.selection = val;
}
else {
//prevent procced to next page if validation is not successful
validateController(pageIndex,true);
}
},
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 20,bottom: 0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
"Maklumat Pembekal",
textAlign: TextAlign.left,
style: TextStyle(
decoration:TextDecoration.underline,
fontWeight: FontWeight.bold,
fontSize: 16,
color: Colors.grey.shade700,
),
),
),
),
TextFormField(
controller: billingDetails.companyNameTxtCtrl,
maxLength: 30,
decoration: InputDecoration(labelText: "Nama Syarikat"),
validator: (String value) {
return value.isEmpty ? 'Nama Syarikat Diperlukan' : null;
},
onSaved: (String value) {
billingDetails.companyName = value;
billingDetails.companyNameTxtCtrl.text = billingDetails.companyName;
},
),
TextFormField(
controller: billingDetails.addressLine1TxtCtrl,
maxLength: 30,
decoration: InputDecoration(labelText: "Alamat Baris 1"),
validator: (String value) {
return value.isEmpty ? 'Alamat Baris tidak boleh kosong.' : null;
},
onSaved: (String value) {
billingDetails.addressLine1 = value;
billingDetails.addressLine1TxtCtrl.text = billingDetails.addressLine1;
},
),
TextFormField(
controller: billingDetails.addressLine2TxtCtrl,
maxLength: 30,
decoration: InputDecoration(labelText: "Alamat Baris 2"),
onSaved: (String value) {
billingDetails.addressLine2 = value;
billingDetails.addressLine2TxtCtrl.text = billingDetails.addressLine2;
},
),
TextFormField(
controller: billingDetails.addressLine3TxtCtrl,
maxLength: 30,
decoration: InputDecoration(labelText: "Alamat Baris 3"),
onSaved: (String value) {
billingDetails.addressLine3 = value;
billingDetails.addressLine3TxtCtrl.text = billingDetails.addressLine3;
},
),
],
),
);
}
yourController.text = yourString;
yourController.selection = TextSelection.fromPosition(TextPosition(offset: yourController.text.length));

How to shift focus to next custom textfield in Flutter?

As per: How to shift focus to next textfield in flutter?, I used FocusScope.of(context).nextFocus() to shift focus. But this doesn't work when you use a reusable textfield class. It only works when you directly use TextField class inside Column.
import 'package:flutter/material.dart';
void main() {
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final focus = FocusScope.of(context);
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
CustomTextField(
textInputAction: TextInputAction.next,
onEditingComplete: () => focus.nextFocus(),
),
const SizedBox(height: 10),
CustomTextField(
textInputAction: TextInputAction.done,
onEditingComplete: () => focus.unfocus(),
),
],
),
),
),
);
}
}
class CustomTextField extends StatelessWidget {
final TextInputAction textInputAction;
final VoidCallback onEditingComplete;
const CustomTextField({
this.textInputAction = TextInputAction.done,
this.onEditingComplete = _onEditingComplete,
});
static _onEditingComplete() {}
#override
Widget build(BuildContext context) {
return TextField(
textInputAction: textInputAction,
onEditingComplete: onEditingComplete,
);
}
}
In this code, if I click next in keyboard it will not shift focus to next textfield. Please help me with this.
That's because the context doesn't have anything it could grab the focus from. Replace your code with this:
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final focus = FocusScope.of(context);
return Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
CustomTextField(
textInputAction: TextInputAction.next,
onEditingComplete: () => focus.nextFocus(),
),
SizedBox(height: 10),
CustomTextField(
textInputAction: TextInputAction.done,
onEditingComplete: () => focus.unfocus(),
),
],
),
);
}
}
You need to wrap your fields in a form widget with a form key and use a TextFormField instead of textField widget. Set the action to TextInputAction.next and it should work! You can also use TextInput.done to trigger the validation.
Here a fully working exemple:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class LogInPage extends StatefulWidget {
LogInPage({Key key}) : super(key: key);
#override
_LogInPageState createState() => _LogInPageState();
}
class _LogInPageState extends State<LogInPage> {
final _formKey = new GlobalKey<FormState>();
bool isLoading = false;
String firstName;
String lastName;
String password;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
backgroundColor: Colors.black,
body: body(),
);
}
Widget body() {
return Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
showInput(
firstName,
TextInputType.name,
Icons.drive_file_rename_outline,
"FirstName",
TextInputAction.next,
onSaved: (value) => firstName = value.trim()),
showInput(lastName, TextInputType.name,
Icons.drive_file_rename_outline, "LastName", TextInputAction.next,
onSaved: (value) => lastName = value.trim()),
showInput(null, TextInputType.text, Icons.drive_file_rename_outline,
"Password", TextInputAction.done,
isPassword: true, onSaved: (value) => password = value),
Padding(
padding: EdgeInsets.symmetric(vertical: 10),
),
showSaveButton(),
],
),
);
}
Widget showInput(String initialValue, TextInputType textInputType,
IconData icon, String label, TextInputAction textInputAction,
{#required Function onSaved, bool isPassword = false}) {
return Padding(
padding: EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0.0),
child: new TextFormField(
style: TextStyle(color: Theme.of(context).primaryColorLight),
maxLines: 1,
initialValue: initialValue,
keyboardType: textInputType,
textInputAction: textInputAction,
autofocus: false,
obscureText: isPassword,
enableSuggestions: !isPassword,
autocorrect: !isPassword,
decoration: new InputDecoration(
fillColor: Theme.of(context).primaryColor,
hintText: label,
hintStyle: TextStyle(color: Theme.of(context).primaryColorDark),
filled: true,
contentPadding: new EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),
border: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(12.0),
),
icon: new Icon(
icon,
color: Theme.of(context).primaryColorLight,
)),
validator: (value) {
return value.isEmpty && !isPassword
? "You didn't filled this field."
: null;
},
onSaved: onSaved,
onFieldSubmitted:
textInputAction == TextInputAction.done ? (value) => save() : null,
),
);
}
Widget showSaveButton() {
return RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(100))),
color: Theme.of(context).primaryColor,
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 25),
child: isLoading
? SizedBox(height: 17, width: 17, child: CircularProgressIndicator())
: Text(
"Sauvegarder",
style: TextStyle(color: Theme.of(context).primaryColorLight),
),
onPressed: save,
);
}
void save() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
//TODO
}
}
}
FocusNode textSecondFocusNode = new FocusNode();
TextFormField textFirst = new TextFormField(
onFieldSubmitted: (String value) {
FocusScope.of(context).requestFocus(textSecondFocusNode);
},
);
TextFormField textSecond = new TextFormField(
focusNode: textSecondFocusNode,
);
// render textFirst and textSecond where you want

contacts_service.dart updates only to white data

I am using contact_service.dart to update the phone contacts as follows:
1. I make sure I have all the permits obtained.
2. I get an Iterable with the contact I want to edit.
Iterable <Contact> johns = await ContactsService.getContacts (query: "john");
3. I edit the data to change
johns .first.givenName = expect "jhon2"
4. I do the update
await ContactsService.updateContact (jhons.first);
The problem is when I check the contact in my phone book.
The contact only has blank data, it seems like the program deletes all the contact information and I don't know why.
Can anybody help me?
Anyone who has worked with the "contact_service" plugin before and can give me a contact update example please?
You can download https://github.com/lukasgit/flutter_contacts/tree/master/example
and replace contacts_list_page.dart with the following full code
When click update icon will call _updateTest to update john's related data
You can see working demo below
code snippet
return Scaffold(
appBar: AppBar(
title: Text(
'Contacts Plugin Example',
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.create),
onPressed: _openContactForm,
),
IconButton(
icon: Icon(Icons.memory),
onPressed: _updateTest,
)
],
_updateTest() async {
Iterable<Contact> johns = await ContactsService.getContacts(query: "john");
if(johns.length > 0) {
var firstConatct = johns.first;
print(firstConatct.middleName);
firstConatct.givenName = "jhon2";
firstConatct.middleName = "hi";
firstConatct.phones = [Item(label: "phone", value: "1234567")];
await ContactsService.updateContact(firstConatct);
refreshContacts();
}
}
working demo
phone
contacts_list_page.dart full code
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:contacts_service_example/main.dart';
import 'package:contacts_service/contacts_service.dart';
import 'package:intl/intl.dart';
class ContactListPage extends StatefulWidget {
#override
_ContactListPageState createState() => _ContactListPageState();
}
class _ContactListPageState extends State<ContactListPage> {
List<Contact> _contacts;
#override
void initState() {
super.initState();
refreshContacts();
}
Future<void> refreshContacts() async {
// Load without thumbnails initially.
var contacts = (await ContactsService.getContacts(
withThumbnails: false, iOSLocalizedLabels: iOSLocalizedLabels))
.toList();
// var contacts = (await ContactsService.getContactsForPhone("8554964652"))
// .toList();
setState(() {
_contacts = contacts;
});
// Lazy load thumbnails after rendering initial contacts.
for (final contact in contacts) {
ContactsService.getAvatar(contact).then((avatar) {
if (avatar == null) return; // Don't redraw if no change.
setState(() => contact.avatar = avatar);
});
}
}
void updateContact() async {
Contact ninja = _contacts
.toList()
.firstWhere((contact) => contact.familyName.startsWith("Ninja"));
ninja.avatar = null;
await ContactsService.updateContact(ninja);
refreshContacts();
}
_openContactForm() async {
try {
var contact = await ContactsService.openContactForm(
iOSLocalizedLabels: iOSLocalizedLabels);
refreshContacts();
} on FormOperationException catch (e) {
switch (e.errorCode) {
case FormOperationErrorCode.FORM_OPERATION_CANCELED:
case FormOperationErrorCode.FORM_COULD_NOT_BE_OPEN:
case FormOperationErrorCode.FORM_OPERATION_UNKNOWN_ERROR:
default:
print(e.errorCode);
}
}
}
_updateTest() async {
Iterable<Contact> johns = await ContactsService.getContacts(query: "john");
if (johns.length > 0) {
var firstConatct = johns.first;
print(firstConatct.middleName);
firstConatct.givenName = "jhon2";
firstConatct.middleName = "hi";
firstConatct.phones = [Item(label: "phone", value: "1234567")];
await ContactsService.updateContact(firstConatct);
refreshContacts();
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Contacts Plugin Example',
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.create),
onPressed: _openContactForm,
),
IconButton(
icon: Icon(Icons.memory),
onPressed: _updateTest,
)
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.of(context).pushNamed("/add").then((_) {
refreshContacts();
});
},
),
body: SafeArea(
child: _contacts != null
? ListView.builder(
itemCount: _contacts?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
Contact c = _contacts?.elementAt(index);
return ListTile(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => ContactDetailsPage(
c,
onContactDeviceSave:
contactOnDeviceHasBeenUpdated,
)));
},
leading: (c.avatar != null && c.avatar.length > 0)
? CircleAvatar(backgroundImage: MemoryImage(c.avatar))
: CircleAvatar(child: Text(c.initials())),
title: Text(c.displayName ?? ""),
);
},
)
: Center(
child: CircularProgressIndicator(),
),
),
);
}
void contactOnDeviceHasBeenUpdated(Contact contact) {
this.setState(() {
var id = _contacts.indexWhere((c) => c.identifier == contact.identifier);
_contacts[id] = contact;
});
}
}
class ContactDetailsPage extends StatelessWidget {
ContactDetailsPage(this._contact, {this.onContactDeviceSave});
final Contact _contact;
final Function(Contact) onContactDeviceSave;
_openExistingContactOnDevice(BuildContext context) async {
try {
var contact = await ContactsService.openExistingContact(_contact,
iOSLocalizedLabels: iOSLocalizedLabels);
if (onContactDeviceSave != null) {
onContactDeviceSave(contact);
}
Navigator.of(context).pop();
} on FormOperationException catch (e) {
switch (e.errorCode) {
case FormOperationErrorCode.FORM_OPERATION_CANCELED:
case FormOperationErrorCode.FORM_COULD_NOT_BE_OPEN:
case FormOperationErrorCode.FORM_OPERATION_UNKNOWN_ERROR:
default:
print(e.toString());
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_contact.displayName ?? ""),
actions: <Widget>[
// IconButton(
// icon: Icon(Icons.share),
// onPressed: () => shareVCFCard(context, contact: _contact),
// ),
IconButton(
icon: Icon(Icons.delete),
onPressed: () => ContactsService.deleteContact(_contact),
),
IconButton(
icon: Icon(Icons.update),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => UpdateContactsPage(
contact: _contact,
),
),
),
),
IconButton(
icon: Icon(Icons.edit),
onPressed: () => _openExistingContactOnDevice(context)),
],
),
body: SafeArea(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Name"),
trailing: Text(_contact.givenName ?? ""),
),
ListTile(
title: Text("Middle name"),
trailing: Text(_contact.middleName ?? ""),
),
ListTile(
title: Text("Family name"),
trailing: Text(_contact.familyName ?? ""),
),
ListTile(
title: Text("Prefix"),
trailing: Text(_contact.prefix ?? ""),
),
ListTile(
title: Text("Suffix"),
trailing: Text(_contact.suffix ?? ""),
),
ListTile(
title: Text("Birthday"),
trailing: Text(_contact.birthday != null
? DateFormat('dd-MM-yyyy').format(_contact.birthday)
: ""),
),
ListTile(
title: Text("Company"),
trailing: Text(_contact.company ?? ""),
),
ListTile(
title: Text("Job"),
trailing: Text(_contact.jobTitle ?? ""),
),
ListTile(
title: Text("Account Type"),
trailing: Text((_contact.androidAccountType != null)
? _contact.androidAccountType.toString()
: ""),
),
AddressesTile(_contact.postalAddresses),
ItemsTile("Phones", _contact.phones),
ItemsTile("Emails", _contact.emails)
],
),
),
);
}
}
class AddressesTile extends StatelessWidget {
AddressesTile(this._addresses);
final Iterable<PostalAddress> _addresses;
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ListTile(title: Text("Addresses")),
Column(
children: _addresses
.map((a) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: <Widget>[
ListTile(
title: Text("Street"),
trailing: Text(a.street ?? ""),
),
ListTile(
title: Text("Postcode"),
trailing: Text(a.postcode ?? ""),
),
ListTile(
title: Text("City"),
trailing: Text(a.city ?? ""),
),
ListTile(
title: Text("Region"),
trailing: Text(a.region ?? ""),
),
ListTile(
title: Text("Country"),
trailing: Text(a.country ?? ""),
),
],
),
))
.toList(),
),
],
);
}
}
class ItemsTile extends StatelessWidget {
ItemsTile(this._title, this._items);
final Iterable<Item> _items;
final String _title;
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ListTile(title: Text(_title)),
Column(
children: _items
.map(
(i) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: ListTile(
title: Text(i.label ?? ""),
trailing: Text(i.value ?? ""),
),
),
)
.toList(),
),
],
);
}
}
class AddContactPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => _AddContactPageState();
}
class _AddContactPageState extends State<AddContactPage> {
Contact contact = Contact();
PostalAddress address = PostalAddress(label: "Home");
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Add a contact"),
actions: <Widget>[
FlatButton(
onPressed: () {
_formKey.currentState.save();
contact.postalAddresses = [address];
ContactsService.addContact(contact);
Navigator.of(context).pop();
},
child: Icon(Icons.save, color: Colors.white),
)
],
),
body: Container(
padding: EdgeInsets.all(12.0),
child: Form(
key: _formKey,
child: ListView(
children: <Widget>[
TextFormField(
decoration: const InputDecoration(labelText: 'First name'),
onSaved: (v) => contact.givenName = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Middle name'),
onSaved: (v) => contact.middleName = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Last name'),
onSaved: (v) => contact.familyName = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Prefix'),
onSaved: (v) => contact.prefix = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Suffix'),
onSaved: (v) => contact.suffix = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Phone'),
onSaved: (v) =>
contact.phones = [Item(label: "mobile", value: v)],
keyboardType: TextInputType.phone,
),
TextFormField(
decoration: const InputDecoration(labelText: 'E-mail'),
onSaved: (v) =>
contact.emails = [Item(label: "work", value: v)],
keyboardType: TextInputType.emailAddress,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Company'),
onSaved: (v) => contact.company = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Job'),
onSaved: (v) => contact.jobTitle = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Street'),
onSaved: (v) => address.street = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'City'),
onSaved: (v) => address.city = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Region'),
onSaved: (v) => address.region = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Postal code'),
onSaved: (v) => address.postcode = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Country'),
onSaved: (v) => address.country = v,
),
],
),
),
),
);
}
}
class UpdateContactsPage extends StatefulWidget {
UpdateContactsPage({#required this.contact});
final Contact contact;
#override
_UpdateContactsPageState createState() => _UpdateContactsPageState();
}
class _UpdateContactsPageState extends State<UpdateContactsPage> {
Contact contact;
PostalAddress address = PostalAddress(label: "Home");
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
#override
void initState() {
super.initState();
contact = widget.contact;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Update Contact"),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.save,
color: Colors.white,
),
onPressed: () async {
_formKey.currentState.save();
contact.postalAddresses = [address];
await ContactsService.updateContact(contact);
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => ContactListPage()));
},
),
],
),
body: Container(
padding: EdgeInsets.all(12.0),
child: Form(
key: _formKey,
child: ListView(
children: <Widget>[
TextFormField(
initialValue: contact.givenName ?? "",
decoration: const InputDecoration(labelText: 'First name'),
onSaved: (v) => contact.givenName = v,
),
TextFormField(
initialValue: contact.middleName ?? "",
decoration: const InputDecoration(labelText: 'Middle name'),
onSaved: (v) => contact.middleName = v,
),
TextFormField(
initialValue: contact.familyName ?? "",
decoration: const InputDecoration(labelText: 'Last name'),
onSaved: (v) => contact.familyName = v,
),
TextFormField(
initialValue: contact.prefix ?? "",
decoration: const InputDecoration(labelText: 'Prefix'),
onSaved: (v) => contact.prefix = v,
),
TextFormField(
initialValue: contact.suffix ?? "",
decoration: const InputDecoration(labelText: 'Suffix'),
onSaved: (v) => contact.suffix = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Phone'),
onSaved: (v) =>
contact.phones = [Item(label: "mobile", value: v)],
keyboardType: TextInputType.phone,
),
TextFormField(
decoration: const InputDecoration(labelText: 'E-mail'),
onSaved: (v) =>
contact.emails = [Item(label: "work", value: v)],
keyboardType: TextInputType.emailAddress,
),
TextFormField(
initialValue: contact.company ?? "",
decoration: const InputDecoration(labelText: 'Company'),
onSaved: (v) => contact.company = v,
),
TextFormField(
initialValue: contact.jobTitle ?? "",
decoration: const InputDecoration(labelText: 'Job'),
onSaved: (v) => contact.jobTitle = v,
),
TextFormField(
initialValue: address.street ?? "",
decoration: const InputDecoration(labelText: 'Street'),
onSaved: (v) => address.street = v,
),
TextFormField(
initialValue: address.city ?? "",
decoration: const InputDecoration(labelText: 'City'),
onSaved: (v) => address.city = v,
),
TextFormField(
initialValue: address.region ?? "",
decoration: const InputDecoration(labelText: 'Region'),
onSaved: (v) => address.region = v,
),
TextFormField(
initialValue: address.postcode ?? "",
decoration: const InputDecoration(labelText: 'Postal code'),
onSaved: (v) => address.postcode = v,
),
TextFormField(
initialValue: address.country ?? "",
decoration: const InputDecoration(labelText: 'Country'),
onSaved: (v) => address.country = v,
),
],
),
),
),
);
}
}