So I'm trying to create a sign up page for my app in flutter. So far, I've got some welcome text and an input form where users can input their email. I've also got a button which will eventually change the page underneath the input field that says 'next'. The idea is to have the button disabled which was simple enough to do (just added OnPressed: null) however when the user enters at least one character, followed by an '#' and then a string list of '.com,.co.uk e.t.c) the next button will become enabled. I have tried to add a validate if else statement to the form but to no joy so have removed it from the code below. I guess what I'm asking is how do I:
Validate the input meets my requirements
Disable the button if it does not
Enable the button if it does
All responses / contributions to any of the above are greatly appreciated!
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyCustomForm extends StatefulWidget {
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
class MyCustomFormState extends State<MyCustomForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Enter your email'),
),
]),
);
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test Bench',
home: Stack(
children: [
Scaffold(
body: Container(
width: double.infinity,
decoration: BoxDecoration(color: Colors.pinkAccent),
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Column(
children: [
Text(
"Hello, Let's Get Started...\n",
style: TextStyle(
fontSize: 60.0,
fontWeight: FontWeight.bold,
fontFamily: 'Oswald',
color: Colors.black,
),
),
MyCustomForm(),
ButtonTheme(
minWidth: 250.0,
child: RaisedButton(
onPressed: null,
child: Text("Next"),
),
),
],
),
),
),
),
],
),
);
}
}
Here is a possible solution. I used regex to validate the email entered.
You can learn more about validation from here:
https://flutter.dev/docs/cookbook/forms/validation
class MyApp extends StatefulWidget {
// This widget is the root of your application.
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _formKey = GlobalKey<FormState>();
bool isValidated = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test Bench',
home: Stack(children: [
Scaffold(
body: Container(
width: double.infinity,
decoration: BoxDecoration(color: Colors.pinkAccent),
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Column(children: [
Text("Hello, Let's Get Started...\n",
style: TextStyle(
fontSize: 60.0,
fontWeight: FontWeight.bold,
fontFamily: 'Oswald',
color: Colors.black,
)),
Form(
key: _formKey,
child: Column(children: <Widget>[
TextFormField(
onChanged: (input) {
_formKey.currentState.validate();
},
validator: (input) {
var regex = RegExp(
r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+#[a-zA-Z0-9]+\.[a-zA-Z]+");
if (!regex.hasMatch(input) && isValidated) {
setState(() {
isValidated = false;
});
return null;
} else {
setState(() {
isValidated = true;
});
return input;
}
},
decoration: InputDecoration(
labelText: 'Enter your email'),
),
])),
ButtonTheme(
minWidth: 250.0,
child: RaisedButton(
onPressed:
isValidated ? () => print('Signed In') : null,
child: Text("Next"),
),
),
]))))
]));
}
}
Related
I'm new to flutter.
I need to get product information through a form using flutter provider.
I can get one object(like String name value only). But when I add multiple parameters, it shows the following error.
Too many positional arguments: 1 expected, but 3 found.
This is the code I wrote.
Model class
class Item {
String itemName;
String description;
double itemPrice;
Item(this.itemName, this.description, this.itemPrice);
}
ChangeNotifier class
class ItemAddNotifier extends ChangeNotifier {
List<Item> itemList = [];
addItem(String itemName, String description, double itemPrice) {
Item item = Item(itemName, description, itemPrice);
itemList.add(item);
notifyListeners();
}
}
Add items
class AddItems extends StatelessWidget {
final TextEditingController _itemNameTextEditing = TextEditingController();
final TextEditingController _itemDescriptionTextEditing =
TextEditingController();
final TextEditingController _itemPriceTextEditing = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Kavishka'),
),
body: Container(
padding: EdgeInsets.all(30.0),
child: Column(
children: [
TextField(
controller: _itemNameTextEditing,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(15.0),
hintText: 'Item Name',
),
),
SizedBox(
height: 20.0,
),
TextField(
controller: _itemDescriptionTextEditing,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(15.0),
hintText: 'Item Description',
),
),
SizedBox(
height: 20.0,
),
TextField(
controller: _itemPriceTextEditing,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(15.0),
hintText: 'Item Price',
),
),
SizedBox(
height: 20.0,
),
RaisedButton(
child: Text('ADD ITEM'),
onPressed: () async {
if (_itemNameTextEditing.text.isEmpty) {
return;
}
await Provider.of<ItemAddNotifier>(context, listen: false)
.addItem(
_itemNameTextEditing.text,
_itemDescriptionTextEditing.text,
_itemPriceTextEditing.text);
Navigator.pop(context);
},
),
],
),
),
);
}
}
Home Screen
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Kavishka'),
actions: [
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) {
return AddItems();
},
),
);
},
icon: Icon(Icons.add))
],
),
body: Container(
padding: EdgeInsets.all(30.0),
child: Column(
children: [
Consumer<ItemAddNotifier>(builder: (context, itemAddNotifier, _) {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: itemAddNotifier.itemList.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.all(15.0),
child: Column(
children: [
Text(
itemAddNotifier.itemList[index].itemName,
style:
TextStyle(fontSize: 20.0, color: Colors.black),
),
],
),
);
});
})
],
),
),
);
}
}
Main
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) {
return ItemAddNotifier();
},
child: MaterialApp(
home: Container(
color: Colors.white,
child: HomeScreen(),
),
),
);
}
}
It shows the error in Item item = Item(itemName, description, itemPrice); line.
If someone can help me to fix this issue.
Thank you.
When I press my card, I want to set my card data into the text field.
my Card Widget and it's another stateful widget. And I need to set this card data into my text field
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
shrinkWrap: true,
children: <Widget>[
Form(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: TextFormField(
decoration: InputDecoration(
labelText: "CupCake Name",
controller: cupCake,
onChanged: (cupcake) {
cupcakeName = cupcake;
},
),
),
),
]
LoadData(),
),
);
}
This is my Card Widget and it's another stateful widget. And I need to set this card data into my text field
class LoadData extends StatefulWidget {
const LoadData({Key? key}) : super(key: key);
#override
_LoadDataState createState() => _LoadDataState();
}
class _LoadDataState extends State<LoadData> {
#override
Widget build(BuildContext context) {
return Container(
child: ListView(
return GestureDetector(
onTap: () => {print("Test")},
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
child: Container(
padding: EdgeInsets.all(16),
child: Column(
children: <Widget>[
Text(
data['cupcake_name'],
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
data['description'],
style: TextStyle(
fontSize: 20,
),
),
],
);
}).toList(),
),
}
}
Take a look at flutter simple state management. Basically as you have state that is shared and pertinent to a sub-tree of your widget tree you should encapsulate and lift that state as high as the first widget that needs to interact with it.
1 create the model which will handle the needed state
class CupCakeState extends ChangeNotifier {
CupCake? cupcake;
void selectCupcake(CupCake newCake){
this.cupCake=newCupcake;
notifyListeners();
}
2 Provide the model in the widget tree
ChangeNotifierProvider(
create: (context) => CupCakeState(),
child: const MyApp(),
),
3 Now you can use it either reading the context.
In your card:
class _LoadDataState extends State<LoadData> {
#override
Widget build(BuildContext context) {
return Container(
child: ListView(
return GestureDetector(
onTap: () => {context.read<CupCakeState>().selectCupcake(CupCake(name:data['cupcake_name'],price:data['cupcake_price']))},
...
Or creating a widget that rebuilds everytime you call notifylisteners()
Consumer<CupCakeState>(
builder: (context, cupcpakeState, child) {
cupcake.value=cupcpakeState.price;//assign the price to the controller
return TextFormField(
decoration: InputDecoration(
labelText: "CupCake Name",
controller: cupCake,
onChanged: (cupcake) {
cupcakeName = cupcake;
},
),
},
)
I'm trying to place an input form field underneath my text which says please enter your email. Could anyone assist? The thing I'm having the biggest problem with right now is I don't know how to add anything else below the text. Ideally, I'd like a centred input field. Code is below:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:google_fonts/google_fonts.dart';
void main() {
runApp(MyApp());
}
class MyCustomForm extends StatefulWidget {
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
class MyCustomFormState extends State<MyCustomForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Enter your email'),
),
]),
);
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test Bench',
home: Stack(children: [
new Scaffold(
body: new Container(
decoration: BoxDecoration(color: Colors.pinkAccent),
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Text(
"Hello, Let's Get Started...",
style: TextStyle(
fontSize: 60.0,
fontWeight: FontWeight.bold,
fontFamily: 'Oswald',
color: Colors.black),
),
),
),
),
]));
}
}
You can wrap the text with a Column to append widgets underneath each other (Video). You can view more information Here. This video also helps with general layouts (link).
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyCustomForm extends StatefulWidget {
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
class MyCustomFormState extends State<MyCustomForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Enter your email'),
),
]),
);
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test Bench',
home: Stack(
children: [
Scaffold(
body: Container(
width: double.infinity,
decoration: BoxDecoration(color: Colors.pinkAccent),
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Hello, Let's Get Started...",
style: TextStyle(
fontSize: 60.0,
fontWeight: FontWeight.bold,
fontFamily: 'Oswald',
color: Colors.black,
),
),
MyCustomForm(),
],
),
),
),
),
],
),
);
}
}
You probably have trouble to find how to add multiples widgets as the child of your Scaffold.
I suggest you to check out the official documentation on layouts
You'll learn there how to create more complex layout and how you can add an input text field under a text widget.
I am making a simple TODO app and I wanted to pass data from 2nd Screen of my app to 1st screen. My first Screen is initially blank and there is nothing to display and it has a floating button to add a task. When it is clicked it takes to the Second page where user inputs the task and author and Clicks on "Submit" Button and takes us to 1st page where it gets Displayed. I want to pass data as List .I am trying everything for last 24 hours I implemented using ModalRoute and also created one instance of ToDo class so that it doesn't give NULL error but nothing is working out. I am attaching code So that you can understand my problem.
This is my FirstScreen()
import 'package:flutter/material.dart';
import 'todo.dart';
import 'todocard.dart';
class ToDos extends StatefulWidget{
#override
_ToDosState createState() => _ToDosState();
}
class _ToDosState extends State<ToDos> {
#override
Widget build(BuildContext context) {
List<ToDo> todos =[
];
final routeArgs = ModalRoute.of(context).settings.arguments as Map ;
todos.add(ToDo(author: routeArgs['task'],task: routeArgs['author']));
return Container(
child: Scaffold(
appBar: AppBar(
title: Text("TODO LIST"),
centerTitle: true,
),
body: Column(
children:todos.map((e) => ToDoCard(
todo: e,
)).toList(),
//ToDoCard is just a Card widget
),
floatingActionButton: FloatingActionButton(
elevation: 0.0,
child: Text("+"),
onPressed: ()
{
Navigator.pushNamed(context, '/add_task');
},
),
),
);
}
}
My SecondScreen is :
import 'package:flutter/material.dart';
class AddTask extends StatefulWidget {
#override
_AddTaskState createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
#override
Widget build(BuildContext context) {
String author,task;
return Container(
child: Scaffold(
appBar: AppBar(
title: Text("ADD TASK"),
centerTitle: true,
),
body: Column(
children: <Widget>[
Text("Enter Your Task"),
TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'task'
),
onChanged: (text){
task = text;
},
),
TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'author'
),
onChanged: (text){
author = text;
},
),
Row(
children: <Widget>[
RaisedButton(
onPressed: () {
Navigator.pop(context, {
'author': author,
'task': task,
});
},
child: Text("Submit"),
),
SizedBox(width: 10.0,),
RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text("Cancel"),
),
],
)
],
),
));
}
}
The main.dart is as Follows:
import 'package:flutter/material.dart';
import 'todo.dart';
import 'add_task.dart';
import 'display_todo.dart';
void main() {
runApp(MaterialApp(
title: 'Passing Data',
initialRoute: '/',
routes: {
'/': (context) => ToDos(),
'/add_task': (context) => AddTask(),
},
));
}
The ToDoCard for displaying the info as Card:
import 'todo.dart';
import 'package:flutter/material.dart';
class ToDoCard extends StatelessWidget {
final ToDo todo;
ToDoCard({this.todo});
#override
Widget build(BuildContext context) {
return Card(
color: Colors.cyan,
margin: EdgeInsets.fromLTRB(20, 20, 20, 0),
child: Padding(
padding: EdgeInsets.fromLTRB(13, 10, 13, 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
todo.author,
style: TextStyle(
color: Colors.black,
fontSize: 20.0,
),
),
SizedBox(height: 10.0,),
Text(
todo.task,
style: TextStyle(
color: Colors.black,
fontSize: 20.0,
),
),
SizedBox(height: 10.0,),
// RaisedButton.icon(onPressed: delete, icon: Icon(Icons.delete), label:
Text("Delete quote"), color: Colors.red,),
],
),
),
);
}
}
ToDo class:
class ToDo{
final String task;
final String author;
ToDo({this.task,this.author});
}
You can pass the result back on the Navigator.pop() and retrieve it by awaiting the pushNamed call.
Retrieve value in Page 1:
onPressed: () async
{
dynamic result = await Navigator.pushNamed(context, '/add_task');
if(result != null) {
setState(() {todos.add(result);});
}
},
Pass value from page 2 in the submit button
onPressed: () {
Navigator.pop(context, ToDo(task: task, author: author));
},
I create a loading dialog and put it in StreamBuilder. At the same time, there is a method named _loadingText as the dialog parameter. When I click the 'Go Run' button, the _loadingText method is called twice.
As the same way, I used the flutter build-in dialog showAboutDialog, everything is OK.
If I remove the StreamBuilder, the _loadingText is called once too.
It takes me one day!!!
Any help is appreciated. Thanks in advance...
main.dart:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:view_animation/loading_dialog.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StreamController<String> _streamController;
TextEditingController _inputController;
#override
void initState() {
super.initState();
_streamController = StreamController<String>.broadcast();
_inputController = TextEditingController();
_inputController.addListener(() {
_streamController.add(_inputController.text);
});
}
#override
void dispose() {
super.dispose();
_streamController.close();
}
String _loadingText() {
print('===== 2. Method run OVER =====');
return 'Loading...';
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_inputContainer(),
SizedBox(
height: 20,
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(26),
),
child: StreamBuilder(
stream: _streamController.stream.map((text) => text.length > 4),
builder: (context, snap) {
return FlatButton(
color: Color(0xFFFFAC0B),
disabledColor: Colors.black12,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
padding: EdgeInsets.symmetric(vertical: 15, horizontal: 12.5),
onPressed: snap.data != null && snap.data
? () {
print('===== 1. show dialog =====');
showDialog(
context: context,
builder: (BuildContext context) {
return LoadingDialog(
loadingText: _loadingText(),
);
});
// showAboutDialog(context: context, applicationName: _loadingText());
}
: null,
child: Text(
'GO RUN',
style: TextStyle(fontSize: 12, color: Colors.white),
),
);
},
),
),
],
)),
);
}
Widget _inputContainer() {
return Container(
width: 200,
padding: EdgeInsets.only(left: 20, right: 20),
decoration: BoxDecoration(
color: Color(0xFFFFAC0B),
borderRadius: BorderRadius.circular(36.0),
),
child: TextField(
controller: _inputController,
keyboardType: TextInputType.number,
maxLines: 1,
cursorColor: Colors.orange,
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Let's GO",
hintStyle: TextStyle(color: Colors.white54, fontSize: 20),
),
),
);
}
}
loading_dialog.dart
import 'package:flutter/material.dart';
class LoadingDialog extends StatefulWidget {
final String loadingText;
final bool outsideDismiss;
final Function dismissCallback;
final Future<dynamic> requestCallback;
LoadingDialog(
{Key key,
this.loadingText = "Loading...",
this.outsideDismiss = true,
this.dismissCallback,
this.requestCallback,
})
: super(key: key);
#override
_LoadingDialogState createState() => _LoadingDialogState();
}
class _LoadingDialogState extends State<LoadingDialog> {
void _dismissDialog(){
if(widget.dismissCallback != null) {
widget.dismissCallback();
}
Navigator.of(context).pop();
}
#override
void initState() {
print('===== 3. loading init =====');
if (widget.requestCallback != null) {
widget.requestCallback.then((_) => Navigator.of(context).pop());
}
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.outsideDismiss ? _dismissDialog : null,
child: Material(
type: MaterialType.transparency,
child: Center(
child: SizedBox(
width: 120.0,
height: 120.0,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new CircularProgressIndicator(),
new Padding(
padding: const EdgeInsets.only(
top: 20.0,
),
child: new Text(
widget.loadingText,
style: new TextStyle(fontSize: 12.0),
),
),
],
),
),
),
),
),
);
}
}
log gif here
That's because when you tap on button first time your TextField is still active that means new state comes and flutter rebuilds itself. When you tap on button second your Textfield is inactive.
The points are when you pass the function to the onTap widget it's going to execute when it building state and calling a function without tapping on it:
So instead of a passing method to the OnTap, try something like this:
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () widget.outsideDismiss ? ()
{
this._dismissDialog();
} : null,
...