Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
// https://hillelcoren.com/2018/06/12/flutter-complex-forms-with-multiple-tabs-and-relationships/
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
// Sample entity classes
class ClientEntity {
ClientEntity({this.name, this.contacts});
String name;
List<ContactEntity> contacts;
}
class ContactEntity {
ContactEntity({this.email});
String email;
}
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_appState');
static final GlobalKey<ClientPageState> _clientKey =
GlobalKey<ClientPageState>();
static final GlobalKey<ContactsPageState> _contactsKey =
GlobalKey<ContactsPageState>();
TabController _controller;
@override
void initState() {
super.initState();
_controller = TabController(vsync: this, length: 2);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Create a test client to show initially
final ClientEntity _client = ClientEntity(
name: 'Acme Client',
contacts: [ContactEntity(email: 'test@example.com')]);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
centerTitle: false,
automaticallyImplyLeading: isMobile(context),
title: Text('Client Form'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.cloud_upload),
onPressed: () {
if (!_formKey.currentState.validate()) {
return;
}
_formKey.currentState.save();
final clientState = _clientKey.currentState;
final contactsState = _contactsKey.currentState;
// If the user never views a tab the state can be null
// in which case we'll use the current value
final ClientEntity client = ClientEntity(
name: clientState?.name ?? _client.name,
contacts: contactsState?.getContacts() ?? _client.contacts,
);
// Do something with the client...
print('Client name: ${client.name}');
},
)
],
bottom: TabBar(
controller: _controller,
tabs: [
Tab(
text: 'Details',
),
Tab(
text: 'Contacts',
),
],
),
),
body: Form(
key: _formKey,
child: TabBarView(
controller: _controller,
children: <Widget>[
ClientPage(client: _client, key: _clientKey),
ContactsPage(client: _client, key: _contactsKey),
],
),
),
),
);
}
}
// Display the client's details, currently just their name
class ClientPage extends StatefulWidget {
const ClientPage({
Key key,
@required this.client,
}) : super(key: key);
final ClientEntity client;
@override
ClientPageState createState() => ClientPageState();
}
class ClientPageState extends State<ClientPage>
with AutomaticKeepAliveClientMixin {
ClientPageState({this.client});
final ClientEntity client;
@override
bool get wantKeepAlive => true;
String name;
@override
Widget build(BuildContext context) {
super.build(context);
return FormCard(
children: <Widget>[
TextFormField(
initialValue: widget.client.name,
onSaved: (value) => name = value.trim(),
decoration: InputDecoration(
labelText: 'Name',
),
),
],
);
}
}
// Displays the list of contacts with a button to add more
class ContactsPage extends StatefulWidget {
const ContactsPage({
Key key,
@required this.client,
}) : super(key: key);
final ClientEntity client;
@override
ContactsPageState createState() => ContactsPageState();
}
class ContactsPageState extends State<ContactsPage>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
List<ContactEntity> _contacts;
List<GlobalKey<ContactFormState>> _contactKeys;
@override
void initState() {
super.initState();
final client = widget.client;
_contacts = client.contacts.toList();
_contactKeys = client.contacts
.map((contact) => GlobalKey<ContactFormState>())
.toList();
}
List<ContactEntity> getContacts() {
final List<ContactEntity> contacts = [];
_contactKeys.forEach((contactKey) {
contacts.add(contactKey.currentState.getContact());
});
return contacts;
}
void _onAddPressed() {
setState(() {
_contacts.add(ContactEntity());
_contactKeys.add(GlobalKey<ContactFormState>());
});
}
void _onRemovePressed(GlobalKey<ContactFormState> key) {
setState(() {
final index = _contactKeys.indexOf(key);
_contactKeys.removeAt(index);
_contacts.removeAt(index);
});
}
@override
Widget build(BuildContext context) {
super.build(context);
final List<Widget> items = [];
for (var i = 0; i < _contacts.length; i++) {
final contact = _contacts[i];
final contactKey = _contactKeys[i];
items.add(ContactForm(
contact: contact,
key: contactKey,
onRemovePressed: (key) => _onRemovePressed(key),
));
}
items.add(Padding(
padding: const EdgeInsets.all(16),
child: ElevatedButton(
child: Text('ADD CONTACT'),
onPressed: _onAddPressed,
),
));
return ScrollableListView(
children: items,
);
}
}
// Displays an individual contact
class ContactForm extends StatefulWidget {
const ContactForm({
Key key,
@required this.contact,
@required this.onRemovePressed,
}) : super(key: key);
final ContactEntity contact;
final Function(GlobalKey<ContactFormState>) onRemovePressed;
@override
ContactFormState createState() => ContactFormState();
}
class ContactFormState extends State<ContactForm> {
String _email;
ContactEntity getContact() {
return ContactEntity(email: _email);
}
@override
Widget build(BuildContext context) {
return FormCard(
children: <Widget>[
TextFormField(
initialValue: widget.contact.email,
onSaved: (value) => _email = value.trim(),
decoration: InputDecoration(
labelText: 'Email',
),
keyboardType: TextInputType.emailAddress,
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 12.0),
child: TextButton(
onPressed: () => widget.onRemovePressed(widget.key),
child: Text(
'Delete',
style: TextStyle(
color: Colors.grey[600],
),
),
),
)
],
),
],
);
}
}
// Helper widget to make the form look a bit nicer
class FormCard extends StatelessWidget {
const FormCard({this.children});
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 12.0, top: 12.0, right: 12.0),
child: Card(
elevation: 2.0,
child: Padding(
padding: const EdgeInsets.only(
left: 12.0, right: 12.0, top: 12.0, bottom: 18.0),
child: Column(
children: children,
),
),
),
);
}
}