In my flutter app, I have an API that I can want to send data from the application through a form.
In my project, there is an iteration of objects and I want the user to add numeric inputs to be sent to the API so there will be several submit buttons to send the data separately.
Currently, I have a Table:
Table(
children: [
const TableRow(children: [
Text(
'',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
Text(
'Size',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
Text(
'Order',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
Text(
'New Size',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
Text(
'New Order',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
Text(
'Submit',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
),
)
]),
// Iterate over the breakdowns list and display the sequence information
for (var breakdown in snapshot
.data![index].breakdowns)
TableRow(children: [
Text(
'${breakdown.order}',
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
Text(
'${breakdown.order}',
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
Text(
'${breakdown.size}',
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const TextField(
keyboardType:
TextInputType.number,
decoration: InputDecoration(),
),
const TextField(
keyboardType:
TextInputType.number,
decoration: InputDecoration(),
),
IconButton(
icon: Icon(Icons
.check_circle_outline),
onPressed: () {
// Code to execute when button is pressed
},
)
])
],
),
Here is the function:
Future<http.Response> addLog(int id,
int logOrder, int logSize) async {
var url = Uri.parse(Config.apiURL +
Config.userAddlogAPI.replaceFirst("{id}", id.toString()));
final response = await http.post(url, headers: {
HttpHeaders.authorizationHeader:
'Token xxxxxxxxxxxxx',
}, body: {
'log_Order': logOrder,
'log_Size': logSize,
});
if (response.statusCode == 200) {
// request was successful, parse the response
return response;
} else {
// request failed, check the status code and response body for more information
throw Exception("Failed to add log");
}
}
My question how can I create a form so that I can send the numeric values received in TextField.
Wrap your Table widget with Form, assign formKey to it.
final _formKey = GlobalKey<FormState>();
Form(key: _formKey, child: Container() //your widget )
for every textInput, use TextFormField and add validator to it.
TextFormField(
keyboardType: TextInputType.number,
validator: (value) {
return value?.trim().isValidInput(context); //YourValidationConditions
}
),
When user tap on submit button check your formValidation and show appropriate message or error.
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
//API Call for correct information
} else {
//DisplayError
}
I have an introduction screen page that displays three texts.To use the introduction screen, i imported the introduction_screen package in the yaml of the project. When running the project, the title is automatically aligning in the center instead of the left side of the screen. How do I align the text on the left side of the screen? This is the code below:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:introduction_screen/introduction_screen.dart';
import 'package:project1/screens/login.dart';
import 'package:project1/utilities/constants.dart';
class Walkthrough extends StatefulWidget {
#override
_WalkthroughState createState() => _WalkthroughState();
}
class _WalkthroughState extends State<Walkthrough> {
List pageInfos = [
{
"title": "This \nis test \nnumber 1",
"body": "",
},
{
"title": "This \nis test \nnumber 2",
"body": "",
},
{
"title": "This \nis test \nnumber 3",
"body": "",
},
];
#override
Widget build(BuildContext context) {
List<PageViewModel> pages = [
for(int i = 0; i<pageInfos.length; i++)
_buildPageModel(pageInfos[i])
];
return WillPopScope(
onWillPop: ()=>Future.value(false),
child: Scaffold(
backgroundColor: Constants.lightPrimary,
body: Padding(
padding: EdgeInsets.fromLTRB(0.0,200.0,0.0,0.0),
child: IntroductionScreen(
pages: pages,
onDone: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (BuildContext context){
return LoginScreen();
},
),
);
},
onSkip: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context){
return LoginScreen();
},
),
);
},
showSkipButton: true,
skip: Text("Skip"),
next: Text(
"Next",
style: TextStyle(
fontWeight: FontWeight.w800,
color: Constants.textPrimary,
),
),
done: Text(
"Done",
style: TextStyle(
fontWeight: FontWeight.w800,
color: Constants.textPrimary,
),
),
),
),
),
);
}
_buildPageModel(Map item){
return PageViewModel(
title: item['title'],
body: item['body'],
decoration: PageDecoration(
titleTextStyle: TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w900,
color: Constants.textPrimary,
),
bodyTextStyle: TextStyle(fontSize: 15.0),
pageColor: Constants.lightPrimary,
),
);
}
}
I want the title item of the page to align on the left side of the screen.
It's because you are providing 'title' which will be styled out as per the library implementation. If you want custom title styling, then use titleWidget instead: \
_buildPageModel(Map item){
return PageViewModel(
//title: item['title'],//<- remove this
titleWidget: Text(item['title'], textAlign: TextAlign.start) // <- Use this instead
body: item['body'],
decoration: PageDecoration(
titleTextStyle: TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w900,
color: Constants.textPrimary,
),
bodyTextStyle: TextStyle(fontSize: 15.0),
pageColor: Constants.lightPrimary,
),
);
}
Although there is an answer already provided but that did not work for me for some reason. That is why I am sharing what worked for me.
Use the below code,
_buildPageModel(Map item){
return PageViewModel(
titleWidget: Container(
alignment: Alignment.centerLeft,
child: Text(
item['title'],
style: TextStyle(
color: Colors.white,
fontSize: 25
),
),
),
bodyWidget: Container(
alignment: Alignment.centerLeft,
child: Text(
item['body'],
style: TextStyle(
color: Colors.white,
fontSize: 25
),
),
),
decoration: PageDecoration(
titleTextStyle: TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w900,
color: Constants.textPrimary,
),
bodyTextStyle: TextStyle(fontSize: 15.0),
pageColor: Constants.lightPrimary,
),
);
}
Let us know if it worked.
I am building a form using the "Flutter Form Builder" package 4.0.2 and trying to add two fields where users enter "tags" via the "material_tag_editor" package 0.0.6
The Problem: when then form is submitted by pressing the "Post" button, neither of the data submitted for those "tag" form fields (Q1 or Q3) is included (see screenshot of the console below).
Notice the line "flutter: {qFour: 30, qFive: sample answer to q5, qTen: sample answer to q10}" - neither Q1 nor Q3 are included (I added their data in separate print statements, so you see them in the console - look for the >>> lines).
Here are screenshots of the form with sample tags entered (iPhone simulator screenshot), and the bottom of the form with the button:
Here's the code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:streakers_journal_beta/screens/reviews_screen.dart';
import 'package:streakers_journal_beta/screens/welcome_screen.dart';
import 'package:streakers_journal_beta/models/user.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
// BEGIN code from material_tag_editor
import 'package:material_tag_editor/tag_editor.dart';
import 'package:material_tag_editor/tag_editor_layout_delegate.dart';
import 'package:material_tag_editor/tag_layout.dart';
import 'package:material_tag_editor/tag_render_layout_box.dart';
// END code from material_tag_editor
//import 'dart:html';
//import 'dart:convert';
// This is the stateful widget that the main application instantiates, per https://api.flutter.dev/flutter/widgets/Form-class.html
class SandboxWriteReviewScreen extends StatefulWidget {
// BEGIN code from material_tag_editor
final String title = 'Material Tag Editor Demo';
// END code from material_tag_editor
#override
_SandboxWriteReviewScreenState createState() =>
_SandboxWriteReviewScreenState();
}
// This is the private State class that goes with WriteReviewScreen
class _SandboxWriteReviewScreenState extends State<SandboxWriteReviewScreen> {
var data;
AutovalidateMode autovalidateMode = AutovalidateMode.always;
bool readOnly = false;
bool showSegmentedControl = true;
//final _newFormbuilderKey = GlobalKey<FormState>();
final _newnewFormbuilderKey = GlobalKey<FormBuilderState>();
// above "GlobalKey" lets us generate a unique, app-wide ID that we can associate with our form, per https://fluttercrashcourse.com/blog/realistic-forms-part1
final ValueChanged _onChanged = (val) => print(val);
// BEGIN related to FormBuilderTextField in form below
final _ageController = TextEditingController(text: '45');
bool _ageHasError = false;
// END related to FormBuilderTextField in form below
String qEleven;
String qTwelve;
// BEGIN code from material_tag_editor
List<String> qOne = [];
final FocusNode _focusNode = FocusNode();
onDelete(index) {
setState(() {
qOne.removeAt(index);
});
}
// below = reiteration for cons
List<String> qThree = [];
//final FocusNode _focusNode = FocusNode();
uponDelete(index) {
// NOTE: "uponDelete" for cons vs. "onDelete" for pros
setState(() {
qThree.removeAt(index);
});
}
// END code from material_tag_editor
//final _user = User();
List<bool> isSelected;
int starIconColor =
0xffFFB900; // was 0xffFFB900; 0xffD49428 is from this image: https://images.liveauctioneers.com/houses/logos/lg/bartonsauction550_large.jpg?auto=webp&format=pjpg&width=140
#override
void initState() {
//isSelected = [true, false];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
Padding(
padding: EdgeInsets.only(right: 12.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.keyboard_backspace),
onPressed: () {
Navigator.pop(context);
},
),
Text(
'back',
style: TextStyle(
fontSize: 7,
),
),
],
),
),
],
leading: Icon(
Icons.rate_review,
color: Colors.black54,
),
title: Column(
children: [
Text(
'SANDBOX Write a Review',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
SizedBox(
height: 6.0,
),
Text(
'flutter_form_builder ^4.0.2',
style: TextStyle(
color: Colors.limeAccent,
fontSize: 14,
),
),
SizedBox(
height: 6.0,
),
],
),
// BEGIN appBar gradient code, per https://medium.com/flutter-community/how-to-improve-your-flutter-application-with-gradient-designs-63180ba96124
flexibleSpace: Container(
decoration: BoxDecoration(
color: Colors.indigoAccent,
),
),
backgroundColor: Colors.white,
centerTitle: false,
),
body: SingleChildScrollView(
child: Container(
child: Builder(
builder: (context) => FormBuilder(
// was "builder: (context) => Form("
key: _newnewFormbuilderKey,
initialValue: {
'date': DateTime.now(),
},
child: Padding(
padding: const EdgeInsets.all(14.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 12.0,
),
RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'Q1 via TagEditor', // was 'What are 3 good or positive things about the house, property or neighborhood?', // [ 1 ]
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
TextSpan(
text: ' (optional)',
style: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 14.0,
color: Colors.black54,
), // was 'misleading or inaccurate?',
),
],
),
),
// BEGIN code from material_tag_editor
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TagEditor(
length: qOne.length,
delimiters: [
','
], // was delimiters: [',', ' '], Also tried "return" ('\u2386',) and '\u{2386}'
hasAddButton: true,
textInputAction: TextInputAction
.next, // moves user from one field to the next!!!!
autofocus: false,
maxLines: 1,
// focusedBorder: OutlineInputBorder(
// borderSide: BorderSide(color: Colors.lightBlue),
// borderRadius: BorderRadius.circular(20.0),
// ),
inputDecoration: const InputDecoration(
// below was "border: InputBorder.none,"
isDense: true,
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
const Radius.circular(20.0),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.lightBlue),
borderRadius: const BorderRadius.all(
const Radius.circular(20.0),
),
// above is per https://github.com/flutter/flutter/issues/5191
),
labelText: 'separate, with, commas',
labelStyle: TextStyle(
fontStyle: FontStyle.italic,
backgroundColor:
Color(0x65dffd02), // was Color(0xffDDFDFC),
color: Colors.black87, // was Color(0xffD82E6D),
fontSize: 14,
),
),
onTagChanged: (value) {
setState(() {
qOne.add(value);
});
},
tagBuilder: (context, index) => _Chip(
index: index,
label: qOne[index],
onDeleted: onDelete,
),
),
),
// END code from material_tag_editor
SuperDivider(),
// END Chips Input
RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'Q3 via TagEditor (skipped Q2, for simplicity)', // [ 2 ] was 'List up to 3 negatives, or things you don’t like, about the house, property or neighborhood:',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
TextSpan(
text: '(optional)', // was text: '\n(optional)',
style: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 14.0,
backgroundColor:
Color(0x70DDFDFC), // was Color(0x30F8A0A2),
color: Colors.black54, // was Color(0xffD82E6D),
//color: Colors.black54,
), // was 'misleading or inaccurate?',
),
],
),
),
// BEGIN code from material_tag_editor
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TagEditor(
length: qThree.length,
delimiters: [','], // was delimiters: [',', ' '],
hasAddButton: true,
textInputAction: TextInputAction
.next, // moves user from one field to the next!!!!
autofocus: false,
maxLines: 1,
// focusedBorder: OutlineInputBorder(
// borderSide: BorderSide(color: Colors.lightBlue),
// borderRadius: BorderRadius.circular(20.0),
// ),
inputDecoration: const InputDecoration(
// below was "border: InputBorder.none,"
isDense: true,
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
const Radius.circular(20.0),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.lightBlue),
borderRadius: const BorderRadius.all(
const Radius.circular(20.0),
),
// above is per https://github.com/flutter/flutter/issues/5191
),
labelText: 'separate, with, commas',
labelStyle: TextStyle(
fontStyle: FontStyle.italic,
backgroundColor:
Color(0x65dffd02), // was Color(0xffDDFDFC),
color: Colors.black87, // was Color(0xffD82E6D),
fontSize: 14,
),
),
onTagChanged: (value) {
setState(() {
qThree.add(value);
});
},
tagBuilder: (context, index) => _Chip(
index: index,
label: qThree[index],
onDeleted: uponDelete,
),
),
),
// END code from material_tag_editor
SuperDivider(),
RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'Q4 - via FormBuilder\'s FormBuilderRadioGroup', // [ 3 ]
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
TextSpan(
text: ' (required)',
style: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 14.0,
color: Colors.red[700],
), // was 'misleading or inaccurate?',
),
],
),
),
FormBuilderRadioGroup(
name: 'qFour',
decoration: const InputDecoration(
border: InputBorder.none,
labelStyle: TextStyle(fontStyle: FontStyle.italic),
),
wrapVerticalDirection: VerticalDirection.down,
// orientation: GroupedRadioOrientation.vertical,
orientation: OptionsOrientation.vertical,
onChanged: _onChanged,
options: [
FormBuilderFieldOption(
value: '0', child: Text('Never')),
FormBuilderFieldOption(
value: '30', child: Text('Within the last month')),
FormBuilderFieldOption(
value: '180',
child: Text('Within the last 6 months')),
FormBuilderFieldOption(
value: '181',
child: Text('More than 6 months ago')),
],
),
SuperDivider(),
Center(
child: RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'Q5 - via FormBuilder\'s FormBuilderTextField', // [ 4 ]
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
TextSpan(
text: ' (optional)',
style: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 14.0,
color: Colors.black54,
), // was 'misleading or inaccurate?',
),
],
),
),
),
GavTextField(
maxCharLength: 200,
fieldAttribute: 'qFive',
fieldLabelText: '',
),
SuperDivider(),
RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'Q10 - via FormBuilder\'s FormBuilderTextField (skipped Q6 - Q9, for simplicity)', // [ 9 ]
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
TextSpan(
text: ' (optional)',
style: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 14.0,
color: Colors.black54,
), // was 'misleading or inaccurate?',
),
],
),
),
GavTextField(
maxCharLength: 1200,
fieldAttribute: 'qTen',
fieldLabelText:
'Be honest & kind.', // was 'Be honest, but kind.',
),
SuperDivider(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.purple,
padding: EdgeInsets.symmetric(
horizontal: 50, vertical: 20),
textStyle: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold)),
onPressed: () {
_newnewFormbuilderKey.currentState.save();
if (_newnewFormbuilderKey.currentState
.validate()) {
print(_newnewFormbuilderKey.currentState.value);
print(
' >>> Q1\'s value via separate print: {$qOne}',
);
print(
' >>> Q3\'s value via separate print: {$qThree}',
);
} else {
print("validation failed");
}
},
child: Text(
'Post',
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
),
],
),
),
SizedBox(
height: 200.0,
),
],
),
),
),
),
),
),
);
}
}
class GavTextField extends StatelessWidget {
GavTextField(
{#required this.maxCharLength,
#required this.fieldAttribute,
#required this.fieldLabelText});
int maxCharLength;
String fieldAttribute;
String fieldLabelText;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FormBuilderTextField(
name: '$fieldAttribute',
// BEGIN countdown to max number of characters, per https://stackoverflow.com/a/64035861/1459653
maxLength: maxCharLength,
maxLines: null,
buildCounter: (
BuildContext context, {
int currentLength,
int maxLength,
bool isFocused,
}) {
return Text(
'${maxLength - currentLength}',
);
},
// END countdown to max number of characters, per https://stackoverflow.com/a/64035861/1459653
decoration: InputDecoration(
labelText:
'$fieldLabelText', // was " Separate items, with, commas",
//counterText: _textController.text.length.toString(),
labelStyle: TextStyle(
fontSize: 12.5,
fontStyle: FontStyle.italic,
),
//helperText: 'Separate, with, commas',
//floatingLabelBehavior: ,
// filled: true,
// fillColor: Colors.lightBlue.withOpacity(0.05),
// BEGIN change border if focus
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.lightBlue),
borderRadius: BorderRadius.circular(20.0),
),
// END change border if focus
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.0),
borderSide: BorderSide(),
),
),
textInputAction:
TextInputAction.next, // moves user from one field to the next!!!!
autofocus:
false, // on screen load, first text field is already active - user can just start typing
),
);
}
} //</formstate>`
var alertStyle = AlertStyle(
animationType: AnimationType.fromTop,
isCloseButton: true,
isOverlayTapDismiss: true,
descTextAlign: TextAlign.start,
alertBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
side: BorderSide(
color: Colors.grey,
),
),
titleStyle: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 16,
color: Colors.black54,
),
alertAlignment: Alignment.topCenter,
);
// BEGIN code from material_tag_editor
class _Chip extends StatelessWidget {
const _Chip({
#required this.label,
#required this.onDeleted,
#required this.index,
});
final String label;
final ValueChanged<int> onDeleted;
final int index;
#override
Widget build(BuildContext context) {
return Chip(
backgroundColor: Colors.blueGrey.shade100,
labelPadding: const EdgeInsets.only(left: 8.0),
label: Text(label),
deleteIcon: Icon(
Icons.cancel_rounded, // was "Icons.close,"
size: 18,
),
onDeleted: () {
onDeleted(index);
},
);
}
}
// END code from material_tag_editor
class SuperDivider extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(
top: 4.0,
bottom: 4.0,
),
child: const Divider(
color: Colors.white70,
height: 30,
thickness: 0.1,
indent: 0,
endIndent: 0,
),
);
}
}
Flutter form builder assumes that only form children's will be used which automatically updates ancestor form whenever their value is changed. But since tag builder is not a form widget, you can do two things -
Wrap these widgets inside a new form widget, whose responsibility will be to only send updates to the ancestor form and render its child.
You can do something like
class GenericFormWidget<T> extends StatefulWidget {
GenericFormWidget({
Key key,
#required this.attribute,
#required this.builder,
}) : super(key: key);
final String attribute;
final Function(BuildContext, Function(T value)) builder;
#override
Widget build(BuildContext context) {
return widget.builder(context, _updateValue);
}
void _updateValue(T value) =>
_formState.setAttributeValue(widget.attribute, value);
}
Instead of using flutter form builder, you can have use Flutter form and create your own field UI using material widgets. This will be costly for you since you will need to migrate existing fields too.