contacts_service.dart updates only to white data - flutter

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,
),
],
),
),
),
);
}
}

Related

The argument type 'string?' can't be assigned to the parameter type 'String"

What are the errors about this code? Please can you make it correct and send me the code?
4 positional argument(s) expected, but 2 found.
Try adding the missing arguments.
Undefined name '_formKey'.
Try correcting the name to one that is defined, or defining the name.
Undefined name '_authData'.
Try correcting the name to one that is defined, or defining the name.
Undefined name '_passwordController'.
Try correcting the name to one that is defined, or defining the name.
Undefined name '_passwordController'.
Try correcting the name to one that is defined, or defining the name.
The function '_submit' isn't defined.
Try importing the library that defines '_submit', correcting the name to the name of an existing function, or defining a function named '_submit'.
Expected a method, getter, setter or operator declaration.
This appears to be incomplete code. Try removing it or completing it.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/authentication.dart';
import 'home_screen.dart';
import 'login_screen.dart';
class SignupScreen extends StatefulWidget {
static const routeName = '/signup';
#override
_SignupScreenState createState() => _SignupScreenState();
}
class _SignupScreenState extends State<SignupScreen> {
final GlobalKey<FormState> _formKey = GlobalKey();
TextEditingController _passwordController = new TextEditingController();
Map<String, String> _authData = {
'email' : '',
'password' : ''
};
void _showErrorDialog(String msg)
{
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text('An Error Occured'),
content: Text(msg),
actions: <Widget>[
FlatButton(
child: Text('Okay'),
onPressed: (){
Navigator.of(ctx).pop();
},
)
],
)
);
}
Future<void> _submit() async
{
if(!_formKey.currentState!.validate())
{
return;
}
_formKey.currentState!.save();
try{
var key = 'email';
await Provider.of<Authentication>(context, listen: false).signUp(
_authData[key],
_authData['password']
);
Navigator.of(context).pushReplacementNamed(HomeScreen.routeName);
} catch(error)
{
var errorMessage = 'Authentication Failed. Please try again later.';
_showErrorDialog(errorMessage);
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Signup'),
actions: <Widget>[
FlatButton(
child: Row(
children: <Widget>[
Text('Login'),
Icon(Icons.person)
],
),
textColor: Colors.white,
onPressed: (){
Navigator.of(context).pushReplacementNamed(LoginScreen.routeName);
},
)
],
),
body: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.limeAccent,
Colors.redAccent,
]
)
),
),
Center(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
child: Container(
height: 300,
width: 300,
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
//email
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
keyboardType: TextInputType.emailAddress,
validator: (value)
{
if(value!.isEmpty || !value.contains('#'))
{
return 'invalid email';
}
return null;
},
onSaved: (value)
{
_authData['email'] = value!;
},
),
//password
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
controller: _passwordController,
validator: (value)
{
if(value!.isEmpty || value.length<=5)
{
return 'invalid password';
}
return null;
},
onSaved: (value)
{
_authData['password'] = value!;
},
),
//Confirm Password
TextFormField(
decoration: InputDecoration(labelText: 'Confirm Password'),
obscureText: true,
validator: (value)
{
if(value!.isEmpty || value != _passwordController.text)
{
return 'invalid password';
}
return null;
},
onSaved: (value)
{
},
),
SizedBox(
height: 30,
),
RaisedButton(
child: Text(
'Submit'
),
onPressed: ()
{
_submit();
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
color: Colors.blue,
textColor: Colors.white,
)
],
),
),
),
),
),
)
],
),
);
}
}
Image
You are getting undefined messages because your build function is outside of the _SignupScreenState class, so just put it inside and those errors will be fixed. And about 4 positional argument(s) expected, but 2 found. Try adding the missing arguments. error, it's probably that your signUp function requires two more arguments, but I don't have source code of it so I can't tell for sure.
Try this:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/authentication.dart';
class SignupScreen extends StatefulWidget {
static const routeName = '/signup';
#override
_SignupScreenState createState() => _SignupScreenState();
}
class _SignupScreenState extends State<SignupScreen> {
final GlobalKey<FormState> _formKey = GlobalKey();
TextEditingController _passwordController = new TextEditingController();
Map<String, String> _authData = {
'email': '',
'password': ''
};
void _showErrorDialog(String msg) {
showDialog(
context: context,
builder: (ctx) =>
AlertDialog(
title: Text('An Error Occured'),
content: Text(msg),
actions: <Widget>[
FlatButton(
child: Text('Okay'),
onPressed: () {
Navigator.of(ctx).pop();
},
)
],
)
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Signup'),
actions: <Widget>[
FlatButton(
child: Row(
children: <Widget>[
Text('Login'),
Icon(Icons.person)
],
),
textColor: Colors.white,
onPressed: () {
Navigator.of(context).pushReplacementNamed(LoginScreen.routeName);
},
)
],
),
body: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.limeAccent,
Colors.redAccent,
]
)
),
),
Center(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
child: Container(
height: 300,
width: 300,
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
//email
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value!.isEmpty || !value.contains('#')) {
return 'invalid email';
}
return null;
},
onSaved: (value) {
_authData['email'] = value!;
},
),
//password
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
controller: _passwordController,
validator: (value) {
if (value!.isEmpty || value.length <= 5) {
return 'invalid password';
}
return null;
},
onSaved: (value) {
_authData['password'] = value!;
},
),
//Confirm Password
TextFormField(
decoration: InputDecoration(
labelText: 'Confirm Password'),
obscureText: true,
validator: (value) {
if (value!.isEmpty ||
value != _passwordController.text) {
return 'invalid password';
}
return null;
},
onSaved: (value) {
},
),
SizedBox(
height: 30,
),
RaisedButton(
child: Text(
'Submit'
),
onPressed: () {
_submit();
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
color: Colors.blue,
textColor: Colors.white,
)
],
),
),
),
),
),
)
],
),
);
}
Future<void> _submit() async
{
if (!_formKey.currentState!.validate()) {
return;
}
_formKey.currentState!.save();
try {
var key = 'email';
await Provider.of<Authentication>(context, listen: false).signUp(
_authData[key],
_authData['password']
);
Navigator.of(context).pushReplacementNamed(HomeScreen.routeName);
} catch (error) {
var errorMessage = 'Authentication Failed. Please try again later.';
_showErrorDialog(errorMessage);
}
}
}
try {
var key = 'email';
await Provider.of<Authentication>(context, listen: false).signUp(
_authData[key],
_authData['password']
);
Navigator.of(context).pushReplacementNamed(HomeScreen.routeName);
} catch (error) {
var errorMessage = 'Authentication Failed. Please try again later.';
_showErrorDialog(errorMessage);
}
}
}
I am referring to your comment on my previous answer, so no, I can't give you my number, that's not the right way to ask for help, I guess that this site is made for that reason. I reproduced your error and solved it with the explanation written in the comments which you can see in the picture bellow.
So the right way to call your signUp method would be:
Provider.of<Authentication>(context, listen:false).signUp(
authdata['email'] as String,
authdata['password'] as String,
'pass third argument here',
'pass fourth argument here',
);

How set form validation in widget flutter

I'm working on flutter project .I have a revision form validator that is not working as expected. When I leave the TextFormField empty the validator doesn't show me anything. I want to stay on the revision form until I enter the values.
thanks in advance
my code :
class Revision extends StatefulWidget {
}
class _RevisionState extends State<Revision> with TickerProviderStateMixin {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
RevisionApi revisionApi = RevisionApi();
TextEditingController _Kilometrage_revisionController =
TextEditingController();
_showAddDialog() async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Colors.white,
title: Text("Ajouter un évènement"),
content: StatefulBuilder(builder: (
BuildContext context,
StateSetter setState,
) {
return SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Expanded(
child: DropdownButtonFormField(
decoration: InputDecoration(
hoverColor: Colors.white,
//contentPadding: EdgeInsets.only(left: 10, right: 15, top: 15),
labelText: 'Type',
alignLabelWithHint: true,
labelStyle: TextStyle(
color: kPrimaryColor,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
),
dropdownColor: Colors.white,
value: status,
items: <DropdownMenuItem>[
DropdownMenuItem(
// value: 'videnge',
value: 0,
child: InkWell(
child: Text('videnge'),
hoverColor: Colors.indigo,
),
),
DropdownMenuItem(
// value: 'visite technique',
value: 1,
child: Text('visite technique'),
),
DropdownMenuItem(
// value: 'assurance véhicule',
value: 2,
child: Text('assurance véhicule'),
),
DropdownMenuItem(
// value: 'autre',
value: 3,
child: Text('autre'),
),
],
onChanged: (value) {
setState(() {
status = value;
});
},
)),
]),
if (status == 1) visiTechniqueDropdown(),
]),
));
}),
actions: <Widget>[
TextButton(
child: Text(
"Enregistrer",
style: TextStyle(
color: Colors.red, fontWeight: FontWeight.bold),
),
onPressed: () {
if (status == null) return;
setState(() {
if (_events[_controller.selectedDay] != null) {
_events[_controller.selectedDay].add(status);
} else {
_events[_controller.selectedDay] = [status];
}
prefs.setString(
"events", json.encode(encodeMap(_events)));
status;
setRevision();
_KilometrageController.clear();
_eventController.clear();
_EmplacementController.clear();
_DateController.clear();
_repeat_revisionController.clear();
_revision_agenceController.clear();
_Kilometrage_revisionController.clear();
Navigator.of(context).pop();
// Navigator.pop(context);
});
},
),
new TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Retour'),
),
],
));
}
void setRevision() async {
print("hello");
if (_formKey.currentState.validate()) {
String kilometrage_pour_vidange = _KilometrageController.text;
String revision_type = status.toString();
String revision_title = _eventController.text;
String revision_location = _EmplacementController.text;
String revision_date = _DateController.text;
String repeat_revision = _repeat_revisionController.text;
String revision_agence = _revision_agenceController.text;
String kilometrage_revision = _Kilometrage_revisionController.text;
revisionApi
.setRevision(
revision_type,
revision_title,
revision_date,
revision_location,
kilometrage_pour_vidange,
repeat_revision,
revision_agence,
kilometrage_revision,
)
.then((data) {
if (data != null) {
Navigator.pop(context);
Navigator.of(context).pop();
Navigator.push(
context, MaterialPageRoute(builder: (context) => Revision()));
}
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(data)));
}).catchError((error) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error.toString())));
});
setState(() {});
}
}
Widget visiTechniqueDropdown() {
return Column(mainAxisSize: MainAxisSize.min, children: [
Row(
children: [
Flexible(
child: TextFormField(
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
validator: (value) {
if (value.isEmpty) {
return 'Password is required';
}
return null;
},
controller: _DateController,
cursorColor: kPrimaryColor,
decoration: InputDecoration(
labelText: 'Date',
alignLabelWithHint: true,
labelStyle: TextStyle(
color: kPrimaryColor,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
How i can set the validator correctly ?
This is for you. Thanks and enjoy
// Create a corresponding State class.
// This class holds data related to the form.
class MyFormState extends State<MyForm> {
// Create a global key that uniquely identifies the Form widget
// and allows validation of the form.
//
// Note: This is a GlobalKey<FormState>,
// not a GlobalKey<MyFormState>.
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
// The validator receives the text that the user has entered.
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate()) {
// If the form is valid, display a snackbar. In the real world,
// you'd often call a server or save the information in a database.
// sendData();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Processing Data')));
}
},
child: Text('Submit'),
),
),
],
),
);
}
}

Is there any way I can reset the dynamic fields I added into a form to their previous state if user doesn't make any changes (Presses back)?

I'm trying to create a dynamic form so I used the idea of using a listview builder to create it. I was able to successfully create it but I faced that I cannot discard changes made to the form by popping it off after editing it. The two textFormField Job name and rate per hour were able to discard changes as they were using onsaved but on the checkbox I can't do that as it has onChanged which wraps setstate to change its state.
You can take a look at the video at this link to see how it functions as of now - https://vimeo.com/523847256
As you can see that it is retaining the data even after popping the page and coming back which I don't want it to. I'm looking for a way to prevent that and make the form the same as before if the user didn't press save.
I have tried to reassign the variables() in onpressed of back button but that didn't work. I also tried push replacement to the same page to reset it but that also didn't work. I think the cuprit here is the sublist and the initialValueTextFormField and initialValueCheckbox which are used declared under ListView.builder but I don't know how to fix that without affecting the dynamic list functionality.
class EditJobPage extends StatefulWidget {
const EditJobPage({Key key, this.job}) : super(key: key);
final Job job;
static Future<void> show(BuildContext context, {Job job}) async {
await Navigator.of(context, rootNavigator: true).pushNamed(
AppRoutes.editJobPage,
arguments: job,
);
}
#override
_EditJobPageState createState() => _EditJobPageState();
}
class _EditJobPageState extends State<EditJobPage> {
final _formKey = GlobalKey<FormState>();
String _name;
int _ratePerHour;
List<dynamic> _subList = [];
Set newSet = Set('', false);
#override
void initState() {
super.initState();
if (widget.job != null) {
_name = widget.job?.name;
_ratePerHour = widget.job?.ratePerHour;
_subList = widget.job?.subList;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 2.0,
title: Text(widget.job == null ? 'New Job' : 'Edit Job'),
leading: IconButton(
icon: Icon(Icons.clear),
onPressed: () {
Navigator.of(context).pop();
},
),
actions: <Widget>[
FlatButton(
child: const Text(
'Save',
style: TextStyle(fontSize: 18, color: Colors.white),
),
onPressed: () => _submit(),
),
],
),
body: _buildContents(),
backgroundColor: Colors.grey[200],
);
}
Widget _buildContents() {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: _buildForm(),
),
),
),
);
}
Widget _buildForm() {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildFormChildren(),
),
);
}
List<Widget> _buildFormChildren() {
print(_subList);
return [
TextFormField(
decoration: const InputDecoration(labelText: 'Job name'),
keyboardAppearance: Brightness.light,
initialValue: _name,
validator: (value) =>
(value ?? '').isNotEmpty ? null : 'Name can\'t be empty',
onChanged: (value) {
setState(() {
_name = value;
});
},
),
TextFormField(
decoration: const InputDecoration(labelText: 'Rate per hour'),
keyboardAppearance: Brightness.light,
initialValue: _ratePerHour != null ? '$_ratePerHour' : null,
keyboardType: const TextInputType.numberWithOptions(
signed: false,
decimal: false,
),
onChanged: (value) {
setState(() {
_ratePerHour = int.tryParse(value ?? '') ?? 0;
});
},
),
Column(
children: <Widget>[
ListView.builder(
shrinkWrap: true,
itemCount: _subList?.length ?? 0,
itemBuilder: (context, index) {
String initialValueTextFormField =
_subList[index].subListTitle.toString();
bool initialValueCheckbox = _subList[index].subListStatus;
return Row(
children: [
Checkbox(
value: initialValueCheckbox,
onChanged: (bool newValue) {
setState(
() {
initialValueCheckbox = newValue;
_subList.removeAt(index);
_subList.insert(
index,
Set(initialValueTextFormField,
initialValueCheckbox));
},
);
},
),
Expanded(
child: TextFormField(
minLines: 1,
maxLines: 1,
initialValue: initialValueTextFormField,
autofocus: false,
textAlign: TextAlign.left,
onChanged: (title) {
setState(() {
initialValueTextFormField = title;
_subList.removeAt(index);
_subList.insert(
index,
Set(initialValueTextFormField,
initialValueCheckbox));
});
},
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelStyle: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
),
filled: true,
hintText: 'Write sub List here',
),
),
),
],
);
},
),
TextButton(
onPressed: () {
setState(() {
_subList.add(newSet);
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.add),
Text('Add Sub Lists'),
],
),
),
],
),
];
}
void _submit() {
final isValid = _formKey.currentState.validate();
if (!isValid) {
return;
} else {
final database = context.read<FirestoreDatabase>(databaseProvider);
final id = widget.job?.id ?? documentIdFromCurrentDate();
final job = Job(
id: id,
name: _name ?? '',
ratePerHour: _ratePerHour ?? 0,
subList: _subList);
database.setJob(job);
Navigator.of(context).pop();
}
}
}
And this is the link to the full repository of the whole flutter app in case you want to look at any other part:- https://github.com/brightseagit/dynamic_forms . Thank you.
Note - This is the edited code of this repo - https://github.com/bizz84/starter_architecture_flutter_firebase.
When assigning the list we need to use _subList = List.from(widget.job.subList) instead of _subList = widget.job.subList.
Otherwise, the changes made in _subList will also be made in job.subList .

Flutter error "A build function returned null"

I have added two dropdown buttons in my app. Selection values of 2nd button depend on 1st button and appear Form related to value in 2nd button. I have used them inside StreamBuilder.
I added multi-imagepicker into above form.
The page can scroll for the first time.but can not scroll after.it throws above error.
it is skipping stream: Firestore.instance.collection("category_names").snapshots(),
line when debugging
I have attached code and screenshots below
import 'package:carousel_pro/carousel_pro.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:multi_image_picker/multi_image_picker.dart';
import 'categories/vehicles/car.dart';
import 'categories/vehicles/van.dart';
import 'services/utils.dart';
class AdAdvertisement extends StatefulWidget {
#override
_AdAdvertisementState createState() => _AdAdvertisementState();
}
class _AdAdvertisementState extends State<AdAdvertisement> {
var selectedCurrency,selectedSub;
var value;
final databaseReference = Firestore.instance;
String now = new DateTime.now().toString();
List<Asset> images = List<Asset>();
//List<NetworkImage> _listOfImages = <NetworkImage>[];
List<String> imageUrls = <String>[];
//List<String> imageLocalLink = <String>[];
String _error = 'No Error Dectected';
bool isUploading = false;
bool carosal = false;
final _formKeyCar = GlobalKey<FormState>();
final _formKeyVan = GlobalKey<FormState>();
void ValueChanged(currencyValue){
setState(() {
selectedCurrency =currencyValue;
});
}
void ValueSubchange(subcategory){
setState(() {
selectedSub=subcategory;
});
}
void createRecord() async {
await databaseReference.collection("Advertisements")
.document(now)
.setData({
'title': 'Mastering Flutter',
'description': 'Programming Guide for Dart'
});
}
Widget _widgetForm() {
switch (selectedSub) {
case "car":
return _carForm();
break;
case "van":
build(context){
return vanForm();
};
//return _vanForm();
break;
}
}
//
//
Future<void> loadAssets() async {
List<Asset> resultList = List<Asset>();
String error = 'No Error Dectected';
try {
resultList = await MultiImagePicker.pickImages(
maxImages: 10,
enableCamera: true,
selectedAssets: images,
cupertinoOptions: CupertinoOptions(takePhotoIcon: "chat"),
materialOptions: MaterialOptions(
actionBarColor: "#abcdef",
actionBarTitle: "Upload Image",
allViewTitle: "All Photos",
useDetailsView: false,
selectCircleStrokeColor: "#000000",
),
);
print(resultList.length);
print((await resultList[0].getThumbByteData(122, 100)));
print((await resultList[0].getByteData()));
print((await resultList[0].metadata));
} on Exception catch (e) {
error = e.toString();
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
images = resultList;
carosal = true;
print('<<<<<<<<<<<<<<<<<<<');
print(images);
//_listOfImages = imageUrls.cast<NetworkImage>();
_error = error;
});
}
Widget _imageShow(){
if(carosal==true){
return CarouselSlider(
items: images
.map((e) => AssetThumb(asset:e, width: 300, height: 300,))
.toList(),
options: CarouselOptions(
height: 400,
aspectRatio: 16 /9 ,
viewportFraction: 0.8,
initialPage: 0,
enableInfiniteScroll: true,
reverse: false,
autoPlay: true,
autoPlayInterval: Duration(seconds: 3),
autoPlayAnimationDuration: Duration(milliseconds: 800),
autoPlayCurve: Curves.fastOutSlowIn,
enlargeCenterPage: true,
scrollDirection: Axis.horizontal,
),
);
}
else{
return Text('not yet selected');
}
}
void uploadImages(){
for ( var imageFile in images) {
postImage(imageFile).then((downloadUrl) {
imageUrls.add(downloadUrl.toString());
if(imageUrls.length==images.length){
String documnetID = DateTime.now().millisecondsSinceEpoch.toString();
Firestore.instance.collection('images').document(documnetID).setData({
'urls':imageUrls
}).then((_){
SnackBar snackbar = SnackBar(content: Text('Uploaded Successfully'));
//widget.globalKey.currentState.showSnackBar(snackbar);
setState(() {
images = [];
imageUrls = [];
carosal =false;
});
});
}
}).catchError((err) {
print(err);
});
}
}
Future<dynamic> postImage(Asset imageFile) async {
String fileName = DateTime.now().millisecondsSinceEpoch.toString();
StorageReference reference = FirebaseStorage.instance.ref().child(fileName);
StorageUploadTask uploadTask = reference.putData((await imageFile.getByteData()).buffer.asUint8List());
StorageTaskSnapshot storageTaskSnapshot = await uploadTask.onComplete;
print(storageTaskSnapshot.ref.getDownloadURL());
return storageTaskSnapshot.ref.getDownloadURL();
}
Widget _carForm() {
return Card(
child: Form(
key: _formKeyVan,
child: Column(children: <Widget>[
// Add TextFormFields and RaisedButton here.
//buildGridView(),
TextFormField(
//validator: (value) {
// if (value.isEmpty) {
// return 'Please enter Brand';
// }
// //return null;
//},
decoration: const InputDecoration(
hintText: 'Enter your Car Brand',
labelText: 'Brand',
prefixIcon: Icon(Icons.add_circle)
),
),
SizedBox(height: 20.0),
TextFormField(
//validator: (value) {
// if (value.isEmpty) {
// return 'Please enter Model';
// }
// return null;
//},
decoration: const InputDecoration(
hintText: 'Enter your Car Model',
labelText: 'Car Model',
prefixIcon: Icon(Icons.add_circle)
),
),
SizedBox(height: 20.0),
TextFormField(
//validator: (value) {
// if (value.isEmpty) {
// return 'Please enter Model Year';
// }
// return null;
//},
decoration: const InputDecoration(
hintText: 'Enter Car Model year',
labelText: 'Model Year',
prefixIcon: Icon(Icons.add_circle)
),
),
SizedBox(height: 20.0),
TextFormField(
//validator: (value) {
// if (value.isEmpty) {
// return 'Please enter Mileage';
// }
// return null;
//},
decoration: const InputDecoration(
hintText: 'Enter Mileage',
labelText: 'Mileage ',
prefixIcon: Icon(Icons.add_circle)
),
),
SizedBox(height: 20.0),
TextFormField(
//validator: (value) {
// if (value.isEmpty) {
// return 'Enter Transmission type';
// }
// return null;
//},
decoration: const InputDecoration(
hintText: 'Enter Transmission type',
labelText: 'Transmission ',
prefixIcon: Icon(Icons.add_circle)
),
),
SizedBox(height: 20.0),
TextFormField(
//validator: (value) {
// if (value.isEmpty) {
// return 'Please enter Fueltype';
// }
// return null;
//},
decoration: const InputDecoration(
hintText: 'Enter Fuel type',
labelText: 'Fueltype ',
prefixIcon: Icon(Icons.add_circle)
),
),
SizedBox(height: 20.0),
TextFormField(
//validator: (value) {
// if (value.isEmpty) {
// return 'Please enter Engine capaciy';
// }
// return null;
//},
decoration: const InputDecoration(
hintText: 'Enter Engine capacity',
labelText: 'Engine capacity(cc) ',
prefixIcon: Icon(Icons.add_circle)
),
),
SizedBox(height: 20.0),
TextFormField(
//validator: (value) {
// if (value.isEmpty) {
// return 'Please enter Description';
// }
// return null;
//},
decoration: const InputDecoration(
hintText: 'Enter Description here',
labelText: 'Description ',
prefixIcon: Icon(Icons.add_circle)
),
),
SizedBox(height: 20.0),
TextFormField(
//validator: (value) {
// if (value.isEmpty) {
// return 'Please Price';
// }
// return null;
//},
decoration: const InputDecoration(
hintText: 'Enter Price',
labelText: 'Price',
prefixIcon: Icon(Icons.add_circle)
),
),
SizedBox(height: 20.0),
RaisedButton(
child: new Text("add Image"),
onPressed: loadAssets,
),
SizedBox(height: 10.0,),
_imageShow(),
RaisedButton(
child: new Text("upload"),
onPressed: (){
if(images.length==0){
showDialog(context: context,builder: (_){
return AlertDialog(
backgroundColor: Theme.of(context).backgroundColor,
content: Text("No image selected",style: TextStyle(color: Colors.white)),
actions: <Widget>[
RaisedButton(
onPressed: (){
Navigator.pop(context);
},
child: Center(child: Text("Ok",style: TextStyle(color: Colors.white),)),
)
],
);
});
}
else{
SnackBar snackbar = SnackBar(content: Text('Please wait, we are uploading'));
//widget.globalKey.currentState.showSnackBar(snackbar);
uploadImages();
}
},
),
SizedBox(height: 20.0),
RaisedButton(
color: Color(0xff11b719),
textColor: Colors.white,
child: Padding(
padding: EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text("Submit", style: TextStyle(fontSize: 24.0)),
],
)),
onPressed: () {
createRecord();
},
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(30.0)
)
),
]
)
)
);
}
//Widget _vanForm() {
// return Form(
// key: _formKeyCar,
// child: Column(children: <Widget>[
// // Add TextFormFields and RaisedButton here.
// TextFormField(
// validator: (value) {
// if (value.isEmpty) {
// return 'Please enter some text';
// }
// return null;
// },
// decoration: const InputDecoration(
// hintText: 'Enter your Van Model',
// labelText: 'Model',
// ),
// ),
//
// ]));
//}
QuerySnapshot qs;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Container(
alignment: Alignment.center,
child: Text('Advertisement'),
),
),
body: ListView(
children: <Widget>[
Text('Select category here'),
Text('Select category here'),
SizedBox(height: 40.0),
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("category_names").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData)
const Text("Loading.....");
else {
List<DropdownMenuItem> currencyItems = [];
List<DropdownMenuItem> currencySub = [];
for(int i=0;i<snapshot.data.documents.length;i++){
DocumentSnapshot snap = snapshot.data.documents[i];
currencyItems.add(
DropdownMenuItem(
child: Text(
//snap.data.values.toString(),
snap.documentID
),
value: "${snap.documentID}",
),
);
}
for (int i = 0; i < snapshot.data.documents.length; i++) {
DocumentSnapshot snap = snapshot.data.documents[i];
if(snap.documentID==selectedCurrency){
for (int j = 0; j < snap.data.length; j++) {
currencySub.add(
DropdownMenuItem(
child: Text(
snap.data['${j + 1}'].toString(),
style: TextStyle(color: Color(0xff11b719)),
),
value: snap.data['${j + 1}'].toString(),
),
);
}
}
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DropdownButton(
items: currencyItems,
onChanged: (currencyValue) => ValueChanged(currencyValue),
value: selectedCurrency,
isExpanded: false,
hint: new Text(
"Choose category Type",
style: TextStyle(color: Color(0xff11b719)),
),
),
DropdownButton(
items: currencySub,
onChanged: (subcategory) => ValueSubchange(subcategory),
value: selectedSub,
isExpanded: false,
hint: new Text(
"Choose sub",
style: TextStyle(color: Color(0xff11b719)),
),
),
],
);
}
}),
_widgetForm(),
],
),
);
}
}
The StreamBuilder has a builder property and this property is of type AsyncWidgetBuilder<T> which is a typedef with the following implementation:
typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);
Therefore you need to return a widget:
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("category_names").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Text("Loading.....");
else if(snapshot.hasData)
return Text("data...");
else
return CircularProgressIndicator();
https://api.flutter.dev/flutter/widgets/AsyncWidgetBuilder.html

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

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.