I have created a addNewProducts function which contains my showModalBottomSheet. I used for my Add a new product form I add a Form which contains different TextFormField for taking input value. When I click on my floatingActionButton it shows the form but when I want to write something the input keyboard cover the Form area so I can not add anything. it's not scrolling.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
class UserScreen extends StatefulWidget {
UserScreen({Key? key}) : super(key: key);
#override
_UserScreenState createState() => _UserScreenState();
}
class _UserScreenState extends State<UserScreen> {
void addNewProducts(BuildContext ctx) {
showModalBottomSheet(
context: ctx,
builder: (_) {
return GestureDetector(
onTap: () {},
behavior: HitTestBehavior.opaque,
child: Card(
child: SingleChildScrollView(
child: Form(
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Product Name',
icon: Icon(Icons.insert_chart)),
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Product Description',
icon: Icon(Icons.description)),
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Minimum Bid Price',
icon: Icon(Icons.price_check)),
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Auction End DateTime',
icon: Icon(Icons.data_saver_off)),
),
SizedBox(
height: 20,
),
ElevatedButton(
onPressed: () {},
child: Text('Add Product'),
),
],
),
),
),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('User Screen'),
actions: [
DropdownButton(
items: [
DropdownMenuItem(
child: Container(
child: Row(
children: <Widget>[
Icon(
Icons.exit_to_app,
color: Theme.of(context).indicatorColor,
),
SizedBox(
width: 8,
),
Text('Logout')
],
),
),
value: 'logout',
),
],
onChanged: (itemIdentifire) {
if (itemIdentifire == 'logout') {
FirebaseAuth.instance.signOut();
}
},
)
],
),
body: Card(),
// ListView.builder(
// itemCount: 10,
// itemBuilder: (ctx, index) => Container(
// padding: EdgeInsets.all(8),
// child: Text('This Works!'),
// ),
// ),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
addNewProducts(context);
},
),
);
}
}
I have changed my Column to ListView now its perfectly work and its now scrollable.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/rendering.dart';
class UserScreen extends StatefulWidget {
UserScreen({Key? key}) : super(key: key);
#override
_UserScreenState createState() => _UserScreenState();
}
class _UserScreenState extends State<UserScreen> {
bool isFabVisibale = false;
void addNewProducts(BuildContext ctx) {
showModalBottomSheet(
isScrollControlled: true,
context: ctx,
builder: (_) {
return GestureDetector(
onTap: () {},
behavior: HitTestBehavior.opaque,
child: Form(
child: ListView(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Product Name',
icon: Icon(Icons.insert_chart)),
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Product Description',
icon: Icon(Icons.description)),
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Minimum Bid Price',
icon: Icon(Icons.price_check)),
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Auction End DateTime',
icon: Icon(Icons.data_saver_off)),
),
SizedBox(
height: 20,
),
ElevatedButton(
onPressed: () {},
child: Text('Add Product'),
),
],
),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('User Screen'),
actions: [
DropdownButton(
items: [
DropdownMenuItem(
child: Container(
child: Row(
children: <Widget>[
Icon(
Icons.exit_to_app,
color: Theme.of(context).indicatorColor,
),
SizedBox(
width: 8,
),
Text('Logout')
],
),
),
value: 'logout',
),
],
onChanged: (itemIdentifire) {
if (itemIdentifire == 'logout') {
FirebaseAuth.instance.signOut();
}
},
),
// DropdownButton(
// items: [
// DropdownMenuItem(
// child: Container(
// child: Row(
// children: <Widget>[
// Icon(
// Icons.add,
// color: Theme.of(context).indicatorColor,
// ),
// SizedBox(
// width: 8,
// ),
// Text('Add New Items')
// ],
// ),
// ),
// value: 'AddProduct',
// ),
// ],
// onChanged: (itemIdentifire) {
// if (itemIdentifire == 'AddProduct') {
// addNewProducts(context);
// }
// },
// ),
],
),
body: NotificationListener<UserScrollNotification>(
onNotification: (notification) {
if (notification.direction == ScrollDirection.forward) {
setState(() {
isFabVisibale = true;
});
}
if (notification.direction == ScrollDirection.reverse) {
setState(() {
isFabVisibale = false;
});
}
return true;
},
child: ListView.builder(
itemCount: 100,
itemBuilder: (ctx, index) => Container(
padding: EdgeInsets.all(8),
child: Text('This Works!'),
),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: isFabVisibale
? FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
addNewProducts(context);
},
)
: null,
);
}
}
Related
when i navigate from NewSales screen to CreateItem screen and add item and press the add button
the item is added to sqflite database then it navigates back to new sales but the state is not updated , it's updated only if i restarted the app
the NewSales screen
class _NewSalesState extends State<NewSales> {
final controller = TextEditingController();
showAlertDialog(BuildContext context,String name) {
// Create button
// Create AlertDialog
AlertDialog alert = AlertDialog(
title: const Text("Alert"),
content: const Text("you want to delete this item?"),
actions: [
TextButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.green)),
child: const Text("CANCEL", style: TextStyle(color: Colors.white)),
onPressed: () {
Navigator.of(context).pop();
},
),
BlocBuilder<SalesCubit, SalesState>(
builder: (context, state) {
final bloc=BlocProvider.of<SalesCubit>(context);
return TextButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.red)),
child: const Text(
"DELETE",
style: TextStyle(color: Colors.white),
),
onPressed: () {
Navigator.of(context).pop();
bloc.deleteItem(name).then((value) {
bloc.getAllItems();
});
},
);
},
)
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
// #override
#override
Widget build(BuildContext context) {
final deviceSize = MediaQuery
.of(context)
.size;
return BlocConsumer<SalesCubit, SalesState>(
listener: (context, state) {},
builder: (context, state) {
final bloc = BlocProvider.of<SalesCubit>(context);
if (state is SalesInitial) {
bloc.getAllItems();
}
return Scaffold(
drawer: const navDrawer(),
appBar: AppBar(
title: const Text("Ticket"),
actions: [
IconButton(
onPressed: () async {
String barcodeScanRes;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
barcodeScanRes = await FlutterBarcodeScanner.scanBarcode(
'#ff6666', 'Cancel', true, ScanMode.BARCODE);
print('barcodeScanRes $barcodeScanRes');
print(bloc.bsResult);
} on PlatformException {
barcodeScanRes = 'Failed to get platform version.';
}
// 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;
bloc.toggleSearch();
controller.text = barcodeScanRes;
bloc.filterItems(barcodeScanRes);
},
icon: const Icon(Icons.scanner),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: IconButton(
onPressed: () {
Navigator.of(context).pushNamed(Routes.addCustomerRoute);
},
icon: Icon(Icons.person_add)),
),
],
),
body: Column(
children: [
// the first button
Container(
width: double.infinity,
height: deviceSize.height * .1,
color: Colors.grey,
child: GestureDetector(
onTap: ()=>displayMessage("an item was charged successfully", context),
child: Padding(
padding: const EdgeInsets.all(10),
child: Container(
color: Colors.green,
child: const Center(
child: Text("charge",style: TextStyle(color: Colors.white),),
),
),
),
),
),
// the second container
SizedBox(
width: deviceSize.width,
height: deviceSize.height * .1,
child: Row(
children: [
DecoratedBox(
decoration:
BoxDecoration(border: Border.all(color: Colors.grey)),
child: SizedBox(
width: deviceSize.width * .8,
child: bloc.isSearch
? TextFormField(
autofocus: true,
controller: controller,
decoration:
const InputDecoration(hintText: "Search"),
onChanged: (value) {
bloc.filterItems(controller.text);
},
)
: DropdownButtonFormField<String>(
// underline: Container(),
// value: "Discounts",
hint: const Padding(
padding: EdgeInsets.only(left: 10),
child: Text('Please choose type'),
),
items: <String>['Discounts', 'All Items']
.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (_) {},
),
),
),
DecoratedBox(
decoration:
BoxDecoration(border: Border.all(color: Colors.grey)),
child: SizedBox(
width: deviceSize.width * .2,
child: IconButton(
onPressed: () {
bloc.toggleSearch();
if (!bloc.isSearch) {
bloc.filterItems('');
}
},
icon: Icon(
!bloc.isSearch ? Icons.search : Icons.close)),
),
)
],
),
),
// the third container
if (state is IsLoading || state is SalesInitial)
const Center(
child: CircularProgressIndicator(
color: Colors.green,
backgroundColor: Colors.green,
),
),
if (state is Loaded || state is IsSearch || state is SearchDone)
bloc.items.isEmpty
? const Center(
child: Text("no items added yet"),
)
: Expanded(
child: bloc.filteredItems.isEmpty
? const Center(
child: Text(
"no items found with this name",
style: TextStyle(color: Colors.green),
),
)
: ListView.builder(
itemCount: bloc.filteredItems.length,
itemBuilder: (context, i) {
final item = bloc.filteredItems[i];
return Card(
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.green,
radius: 20,
child: Text(item.price),
),
title: Text(item.name),
subtitle: Text(item.barcode),
trailing: Column(
children: [
IconButton(
onPressed: () {
showAlertDialog(context,item.name);
// bloc.deleteItem(item.name).then((value) {
// bloc.getAllItems();
// });
},
icon: const Icon(
Icons.delete,
color: Colors.red,
))
],
)),
);
}))
],
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: ()async {
Navigator.of(context).pushNamed(Routes.createItemRoute);
},
),
);
},
);
}
}
the CreateItem screen
class _CreateItemState extends State<CreateItem> {
final nameController = TextEditingController();
final priceController = TextEditingController();
final costController = TextEditingController();
final skuController = TextEditingController();
final barcodeController = TextEditingController();
final inStockController = TextEditingController();
bool each = true; // bool variable for check box
bool isColor = true;
bool switchValue = false;
File? file;
String base64File = "";
String path = "";
final _formKey = GlobalKey<FormState>();
#override
void dispose() {
nameController.dispose();
priceController.dispose();
costController.dispose();
skuController.dispose();
barcodeController.dispose();
inStockController.dispose();
super.dispose();
}
Future pickImage(ImageSource source) async {
File? image1;
XFile imageFile;
final imagePicker = ImagePicker();
final image = await imagePicker.pickImage(source: source);
imageFile = image!;
image1 = File(imageFile.path);
List<int> fileUnit8 = image1.readAsBytesSync();
setState(() {
base64File = base64.encode(fileUnit8);
});
}
#override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
return BlocProvider(
create: (BuildContext context) => CreateItemCubit(),
child: Builder(builder: (context){
final bloc=BlocProvider.of<CreateItemCubit>(context);
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () => Navigator.of(context).pushNamedAndRemoveUntil(Routes.newSalesRoute, (route) => false),
icon: const Icon(Icons.arrow_back)),
title: const Text("Create Item"),
actions: [TextButton(onPressed: () {}, child: const Text("SAVE"))],
),
body: Padding(
padding: const EdgeInsets.all(10),
child: Form(
key: _formKey,
child: ListView(
children: [
TextFormField(
controller: nameController,
cursorColor: ColorManager.black,
decoration: const InputDecoration(hintText: "Name"),
validator: (value) {
if (value!.isEmpty || value.length < 5) {
return "name can't be less than 5 chars";
}
return null;
},
),
gapH24,
Text(
"Category",
style: TextStyle(color: ColorManager.grey),
),
SizedBox(
width: deviceSize.width,
child: DropdownButtonFormField<String>(
// value: "Categories",
hint: const Padding(
padding: EdgeInsets.only(left: 10),
child: Text('No Category'),
),
items: <String>['No Category', 'Create Category']
.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (_) {},
),
),
gapH24,
const Text(
"Sold by",
style: TextStyle(color: Colors.black),
),
gapH24,
Row(
children: [
Checkbox(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5.0))), // Rounded Checkbox
value: each,
onChanged: (inputValue) {
setState(() {
each = !each;
});
},
),
gapW4,
const Text(
"Each",
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
gapH24,
Row(
children: [
Checkbox(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5.0))), // Rounded Checkbox
value: !each,
onChanged: (inputValue) {
setState(() {
each = !each;
});
},
),
gapW4,
const Text(
"Weight",
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
TextFormField(
controller: priceController,
decoration: InputDecoration(
hintText: "Price",
hintStyle: TextStyle(color: ColorManager.grey)),
validator: (value) {
if (value!.isEmpty) {
return "price can,t be empty";
}
return null;
},
),
gapH4,
Text(
"leave the field blank to indicate price upon sale",
style: TextStyle(color: ColorManager.grey),
),
gapH20,
Text(
"Cost",
style: TextStyle(color: ColorManager.grey),
),
TextFormField(
controller: costController,
decoration: InputDecoration(
hintText: "cost",
hintStyle: TextStyle(color: ColorManager.black)),
validator: (value) {
if (value!.isEmpty) {
return "cost can't be empty";
}
return null;
},
),
gapH20,
Text(
"SKU",
style: TextStyle(color: ColorManager.grey),
),
TextFormField(
controller: skuController,
decoration: InputDecoration(
hintText: "Sku",
hintStyle: TextStyle(color: ColorManager.black)),
validator: (value) {
if (value!.isEmpty) {
return "Sku can't be empty";
}
return null;
},
),
gapH30,
TextFormField(
controller: barcodeController,
decoration: InputDecoration(
hintText: "Barcode",
hintStyle: TextStyle(color: ColorManager.grey)),
validator: (value) {
if (value!.isEmpty) {
return "Barcode can't be empty";
}
return null;
},
),
gapH30,
// Divider(thickness: 1,color: ColorManager.black,),
Text(
"Inventory",
style: TextStyle(
color: ColorManager.green,
fontSize: 15,
fontWeight: FontWeight.bold),
),
// ListTile(
// title: Text("TrackStock",style: TextStyle(color: ColorManager.green,fontSize: 15,fontWeight: FontWeight.bold)),
// trailing: Switch(value: switchValue, onChanged: (bool value) {
// setState(() {
// switchValue=!switchValue;
// });
// },),
// ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("TrackStock",
style: TextStyle(
color: ColorManager.grey,
fontSize: 15,
fontWeight: FontWeight.bold)),
Switch(
value: switchValue,
onChanged: (bool value) {
setState(() {
switchValue = !switchValue;
});
},
),
],
),
gapH10,
if (switchValue)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"in stock",
style:
TextStyle(color: ColorManager.grey, fontSize: 15),
),
TextFormField(
keyboardType: TextInputType.number,
controller: inStockController,
decoration: const InputDecoration(hintText: "0"),
validator: (value) {
if (value!.isEmpty) {
return "value can't be empty";
}
return null;
},
)
],
),
gapH20,
Text(
"Representation in POS",
style: TextStyle(
color: ColorManager.green,
fontSize: 15,
fontWeight: FontWeight.bold),
),
gapH15,
Row(
children: [
Checkbox(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5.0))), // Rounded Checkbox
value: isColor,
onChanged: (inputValue) {
setState(() {
if (!isColor) {
isColor = !isColor;
}
});
},
),
gapW4,
const Text(
"Color and Shape",
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
gapH10,
Row(
children: [
Checkbox(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5.0))), // Rounded Checkbox
value: !isColor,
onChanged: (inputValue) {
setState(() {
if (isColor) {
isColor = !isColor;
}
});
},
),
gapW4,
const Text(
"Image",
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
isColor
? GridView.builder(
physics:
const NeverScrollableScrollPhysics(), // to disable GridView's scrolling
shrinkWrap: true, // You won't see infinite size error
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
crossAxisSpacing: 5.0,
mainAxisSpacing: 5.0,
),
itemCount: containers().length,
itemBuilder: (BuildContext context, int index) {
return containers()[index];
},
)
: Padding(
padding: const EdgeInsets.all(10),
child: Row(
children: [
Expanded(
flex: 1,
child: base64File == ""
? const Icon(Icons.person)
: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(5)),
child: Image.memory(
base64.decode(base64File)))),
gapW4,
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextButton(
onPressed: () =>
pickImage(ImageSource.camera),
child: Row(
children: [
Icon(
Icons.camera_alt,
color: ColorManager.black,
),
gapW8,
Text(
"Take a photo",
style: TextStyle(
color: ColorManager.black,
),
)
],
),
),
const Divider(
thickness: 1,
color: Colors.grey,
),
TextButton(
onPressed: () =>
pickImage(ImageSource.gallery),
child: Row(
children: [
Icon(Icons.camera_alt,
color: ColorManager.black),
gapW8,
Text(
"Choose from gallery",
style: TextStyle(
color: ColorManager.black,
),
)
],
),
)
],
),
)
],
),
)
],
),
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.save,color: Colors.white,),
onPressed: () async {
bool isValid = _formKey.currentState!.validate();
if (isValid) {
FocusScope.of(context).unfocus();
ItemModel? item=await bloc.checkItem(nameController.text);
if(item!=null){
displayMessage("this item was inserted before", context);
}else{
int? res=await bloc.saveItem(ItemModel(
nameController.text,
priceController.text,
costController.text,
skuController.text,
barcodeController.text));
if(res!=null){
displayMessage("an item was inserted successfully", context);
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context)=>const NewSales()), (route) => false);
}
}
}
},
),
);
}
),
);
}
}
this is the SaleCubit
class SalesCubit extends Cubit<SalesState> {
SalesCubit() : super(SalesInitial());
bool isSearch=false;
ItemDBHelper db=ItemDBHelper();
List<ItemModel> items=[];
List<ItemModel> filteredItems=[];
String bsResult='Unknown'; //barcode search result
void toggleSearch(){
isSearch=!isSearch;
emit(IsSearch());
}
void setBarcodeResult(String value){
bsResult=value;
emit(SearchDone());
}
void filterItems(String query){
// emit(Searching());
query.isEmpty?
filteredItems=items:
filteredItems=items.where((item) => item.name.toLowerCase().contains(query.toLowerCase())).toList();
emit(SearchDone());
}
Future<List<ItemModel>?> getAllItems()async{
try{
emit(IsLoading());
items=await db.getAllItems();
filteredItems=items;
print(items);
return items;
}finally{
emit(Loaded());
}
}
Future<void> deleteItem(String name)async{
await db.deleteItem(name);
emit(SearchDone());
}
}
this is the SalesState
part of 'sales_cubit.dart';
#immutable
abstract class SalesState {}
class SalesInitial extends SalesState {}
class IsSearch extends SalesState {}
class IsLoading extends SalesState {}
class Loaded extends SalesState {}
class Searching extends SalesState {}
class SearchDone extends SalesState {}
Am a newbie though. I followed a tutorial for my code below which is a register form. How can i validate each input field since it's just one widget which is reused for the entire input field.
............................................................................................................................................................
Code
import 'package:flutter/material.dart';
import 'package:flutterlogindesign/utils/color.dart';
import 'package:flutterlogindesign/widgets/btn_widget.dart';
import 'package:flutterlogindesign/widgets/herder_container.dart';
class RegPage extends StatefulWidget {
#override
_RegPageState createState() => _RegPageState();
}
class _RegPageState extends State<RegPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.only(bottom: 30),
child: Column(
children: <Widget>[
HeaderContainer("Register"),
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.only(left: 20, right: 20, top: 30),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
_textInput(hint: "Fullname", icon: Icons.person),
_textInput(hint: "Email", icon: Icons.email),
_textInput(hint: "Phone Number", icon: Icons.call),
_textInput(hint: "Password", icon: Icons.vpn_key),
Expanded(
child: Center(
child: ButtonWidget(
btnText: "REGISTER",
onClick: () {
Navigator.pop(context);
},
),
),
),
RichText(
text: TextSpan(children: [
TextSpan(
text: "Already a member ? ",
style: TextStyle(color: Colors.black)),
TextSpan(
text: "Login",
style: TextStyle(color: orangeColors)),
]),
)
],
),
),
)
],
),
),
);
}
Widget _textInput({controller, hint, icon}) {
return SingleChildScrollView(
child: Container(
margin: EdgeInsets.only(top: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: Colors.white,
),
padding: EdgeInsets.only(left: 10),
child: TextFormField(
controller: controller,
decoration: InputDecoration(
border: InputBorder.none,
hintText: hint,
prefixIcon: Icon(icon),
),
),
),
);
}
}
Use Form Widget like the following
final _formKey = GlobalKey<FormState>();
bool autoValidate = false;
String phone;
child:Form( key: _formKey,
autovalidateMode: autoValidate ? AutovalidateMode.always : AutovalidateMode.disabled,
child: Column(
children: [
AppTextFormField(
initialValue: phone,
onChanged: (value) {
phone = value;
},
validator: (value) {
if (value.isEmpty) {
return 'Enter Phone number';
}
},
),
PrimaryButton(
text: 'Go',
onTap: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
} else {
autoValidate = true;
}
},
),
],
),
),
I have created a form widget that I am using to create multiple forms in a list view. I am following this implementation: https://github.com/putraxor/flutter_multipage_form
Apparently form validation is not working. When I try to validate each form, I am finding (after debugging) that the widget is not mounted thus I am getting the error:
The method 'validate' was called on null. Receiver: null Tried
calling: validate()
Here is the code:
Form Widget
typedef OnDelete();
class UserForm extends StatefulWidget {
final User user;
final state = _UserFormState();
final OnDelete onDelete;
UserForm({Key key, this.user, this.onDelete}) : super(key: key);
#override
_UserFormState createState() => state;
bool isValid() => state.validate();
}
class _UserFormState extends State<UserForm> {
final form = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(16),
child: Material(
elevation: 1,
clipBehavior: Clip.antiAlias,
borderRadius: BorderRadius.circular(8),
child: Form(
key: form,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
AppBar(
leading: Icon(Icons.verified_user),
elevation: 0,
title: Text('User Details'),
backgroundColor: Theme.of(context).accentColor,
centerTitle: true,
actions: <Widget>[
IconButton(
icon: Icon(Icons.delete),
onPressed: widget.onDelete,
)
],
),
Padding(
padding: EdgeInsets.only(left: 16, right: 16, top: 16),
child: TextFormField(
initialValue: widget.user.fullName,
onSaved: (val) => widget.user.fullName = val,
validator: (val) =>
val.length > 3 ? null : 'Full name is invalid',
decoration: InputDecoration(
labelText: 'Full Name',
hintText: 'Enter your full name',
icon: Icon(Icons.person),
isDense: true,
),
),
),
Padding(
padding: EdgeInsets.only(left: 16, right: 16, bottom: 24),
child: TextFormField(
initialValue: widget.user.email,
onSaved: (val) => widget.user.email = val,
validator: (val) =>
val.contains('#') ? null : 'Email is invalid',
decoration: InputDecoration(
labelText: 'Email Address',
hintText: 'Enter your email',
icon: Icon(Icons.email),
isDense: true,
),
),
)
],
),
),
),
);
}
///form validator
bool validate() {
var valid = form.currentState.validate();
if (valid) form.currentState.save();
return valid;
}
}
Multi form widget
class MultiForm extends StatefulWidget {
#override
_MultiFormState createState() => _MultiFormState();
}
class _MultiFormState extends State<MultiForm> {
List<UserForm> users = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: .0,
leading: Icon(
Icons.wb_cloudy,
),
title: Text('REGISTER USERS'),
actions: <Widget>[
FlatButton(
child: Text('Save'),
textColor: Colors.white,
onPressed: onSave,
)
],
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0xFF30C1FF),
Color(0xFF2AA7DC),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: users.length <= 0
? Center(
child: EmptyState(
title: 'Oops',
message: 'Add form by tapping add button below',
),
)
: ListView.builder(
addAutomaticKeepAlives: true,
itemCount: users.length,
itemBuilder: (_, i) => users[i],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: onAddForm,
foregroundColor: Colors.white,
),
);
}
///on form user deleted
void onDelete(User _user) {
setState(() {
var find = users.firstWhere(
(it) => it.user == _user,
orElse: () => null,
);
if (find != null) users.removeAt(users.indexOf(find));
});
}
///on add form
void onAddForm() {
setState(() {
var _user = User();
users.add(UserForm(
user: _user,
onDelete: () => onDelete(_user),
));
});
}
///on save forms
void onSave() {
if (users.length > 0) {
var allValid = true;
users.forEach((form) => allValid = allValid && form.isValid());
if (allValid) {
var data = users.map((it) => it.user).toList();
Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (_) => Scaffold(
appBar: AppBar(
title: Text('List of Users'),
),
body: ListView.builder(
itemCount: data.length,
itemBuilder: (_, i) => ListTile(
leading: CircleAvatar(
child: Text(data[i].fullName.substring(0, 1)),
),
title: Text(data[i].fullName),
subtitle: Text(data[i].email),
),
),
),
),
);
}
}
}
}
How can I make this work?
Is there a way I can create and access a validate method in the state of the form widget from the other widget and use it for validation?
I'm working on flutter project and came across a problem. the textfield supposed to take a value from controller which work perfectly but onChanged function wont work. However it is not working.i must retype value to show results .
how can i get result from onchanged when my textfield brings the value ?
code:
// search function that call api
class _SearchPageTState extends State<SearchPageT> {
GlobalState _store = GlobalState.instance;
List<dynamic> searchResults = [];
searchT(value) async {
SearchServicet.searchApiT(value).then((responseBody) {
List<dynamic> data = jsonDecode(responseBody);
setState(() {
data.forEach((value) {
searchResults.add(value);
});
});
});
}
#override
///////
//code textfield search onchanged
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
centerTitle: true,
),
body: ListView(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10.0),
child: TextField(
autofocus: true,
controller: TextEditingController()
..text = '${_store.get('num')}',
onChanged: (value) {
searchResults.clear();
searchCodeighniterT(value);
},
textAlign: TextAlign.center,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 25.0),
labelText: 'number',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
),
suffixIcon: IconButton(
icon: Icon(Icons.search),
onPressed: null,
),
),
),
),
SizedBox(
height: 10.0,
),
ListView.builder(
physics: ScrollPhysics(),
shrinkWrap: true,
itemCount: searchResults.length,
itemBuilder: (BuildContext context, int index) {
return buildResultCard(context, searchResults[index]);
},
),
],
),
),
);
}
//here where shows the result
Widget buildResultCard(BuildContext context, data) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
new Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/bgwlgo.png"), fit: BoxFit.cover),
),
margin: EdgeInsets.all(5.0),
child: Column(
children: <Widget>[
new Card(
child: ListTile(
title: Text(
"DATE:",
),
subtitle: Text(
data['DATETIME'].toString(),
),
),
),
new Card(
child: ListTile(
title: Text(
"TRAITEMENT:",
),
subtitle: Text(
data['TRAITEMENT_DETAIL'].toString()
),
),
),
new Padding(
padding: EdgeInsets.all(9.0),
),
],
),
),
Divider(
color: Colors.black,
)
],
),
);
}
As discussed and understood in the comments, here are a couple of possible solutions for the issue.
If you are using a stateful Widget and need a controller for your TextFormField, you can use it to set the text on the TextFormField. Otherwise, you can just use the initialValue property:
class TextFieldIssue64061076 extends StatelessWidget {
final TextEditingController _textEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children:[
Text('Page 1'),
TextFormField(
controller: _textEditingController,
),
FlatButton(
child: Text('Submit'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return SecondPage64061076(text: _textEditingController.text);
}
)),
),
],
);
}
}
class SecondPage64061076 extends StatefulWidget {
final String text;
SecondPage64061076({
this.text
});
#override
_SecondPage64061076State createState() => _SecondPage64061076State();
}
class _SecondPage64061076State extends State<SecondPage64061076> {
final TextEditingController _textEditingController = TextEditingController();
#override
void initState() {
// You can either use a text controller to set the text on the text field
// or the initialValue below
if(widget.text != null && widget.text != ''){
_textEditingController.text = widget.text;
methodToCallOnChanged(widget.text);
}else{
_textEditingController.text = '';
}
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Page 2'),
TextFormField(
controller: _textEditingController,
onChanged: (value) => methodToCallOnChanged(value),
// As mentioned you could just use the initial value, it you don't need
// a Stateful Widget
// initialValue: widget.text ?? '',
),
]
),
);
}
void methodToCallOnChanged(data){
print('I was called onChange or when the view opened and there was data');
}
}
Widget build(BuildContext context) {
bool isRecording = false;
bool isShowSendButton = false;
/// make sure textcontroller and any variabloes or booleans are not defined
///in the build method
}
You can't be using the constructor of the TextEditingController and assign it to the controller.
The controller of the TextField only takes the variable assigned to the TextEditingController. Check the following code.
controller:theNameOfYourController
Expanded(
flex: 8,
child: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 10),
child: TextFormField(
onChanged: (value){
messageFieldController.text=value;
},
//controller: messageFieldController,
minLines: 1,
maxLines: 2,
textCapitalization: TextCapitalization.sentences,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
prefixIcon: IconButton(
onPressed: () {},
icon: const Icon(Icons.add, color: Colors.blue),
),
suffixIcon: processing == true
? const CircularProgressIndicator()
: IconButton(
onPressed: () {
try {
/*for (int i = 0;
i < widget.selectedMembers.length;
i++) {
sendMessage(
widget.selectedMembers[i].number);
}*/
for (int i = 0;
i < widget.selectedGroups.length;
i++) {
if (widget.selectedGroups.isNotEmpty) {
sendMessageToGroup(
widget.selectedGroups[i].id,
widget.selectedGroups[i].groupName);
}
}
} catch (err) {
Fluttertoast.showToast(
msg: 'Unable to send Message');
setState(() {
processing = false;
});
}
},
icon:
const Icon(Icons.send, color: Colors.blue),
),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
),
),
),
)
I am having problems with the validate() function. I can't understand why it skips some validator:(){}. Here is my code:
import 'package:flutter/material.dart';
class TestScreen extends StatefulWidget {
static const routeName = '/test-screen';
#override
_TestScreenState createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
final _form = GlobalKey<FormState>();
Future<void> _saveForm() async {
final isValid = _form.currentState.validate();
if (!isValid) {
return;
}
}
Widget _buildContainer(Widget child) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(10),
),
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(10),
height: 200,
width: 300,
child: child,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Test',
style: Theme.of(context).textTheme.title,
),
automaticallyImplyLeading: false,
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Form(
key: _form,
child: ListView(
children: <Widget>[
TextFormField(
initialValue: '',
decoration: InputDecoration(labelText: 'Name'),
validator: (value) {
if (value.isEmpty) {
return 'Insert something';
}
return null;
},
),
TextFormField(
initialValue: '',
decoration: InputDecoration(labelText: 'Number'),
keyboardType: TextInputType.number,
validator: (value) {
if (int.tryParse(value) == null) {
return 'Insert a number';
}
return null;
},
),
SizedBox(height: 20),
_buildContainer(
ListView(
children: <Widget>[],
),
),
Text(
'Text-Test1',
textAlign: TextAlign.center,
),
Slider(
value: 1,
divisions: 3,
min: 0.0,
max: 3.0,
label: 'Test',
onChanged: (newValue) {},
),
SizedBox(
height: 20,
),
Text(
'Text-Test2',
textAlign: TextAlign.center,
),
Slider(
value: 3,
divisions: 4,
min: 0.0,
max: 4.0,
label: 'Nothing2',
onChanged: (newValue) {},
),
SizedBox(
height: 20,
),
Row(
children: <Widget>[
Text('RandomLabel'),
Spacer(),
Container(
width: 100,
child: TextFormField(
initialValue: '',
keyboardType: TextInputType.number,
validator: (value) {
if (int.tryParse(value) == null) {
return 'Insert a number';
}
return null;
},
),
),
],
),
SizedBox(
height: 20,
),
Text(
'Test 2',
textAlign: TextAlign.center,
),
_buildContainer(
ListView.builder(
itemCount: 0,
itemBuilder: (ctx, index) {
return ListTile(
title: Text('hello'),
subtitle: Text('world'),
);
},
),
),
TextFormField(
initialValue: '',
minLines: 4,
maxLines: 4,
decoration: InputDecoration(labelText: 'Text'),
validator: (value) {
if (value.isEmpty) {
return 'Insert something';
}
return null;
},
),
FlatButton.icon(
icon: Icon(Icons.check),
label: Text('Done'),
onPressed: () {
_saveForm();
},
),
],
),
),
),
);
}
}
If I click on Done, it skips the first two TextFormFields and goes to validate the one in the Row. Obviously it is not what I want. How to fix it and validate all the TextFormFields?
Wrapping form fields with ListView is a bad idea: when user scrolls to submit button some inputs are disposed because they are off-screen. You have to replace ListView with Column widget and wrap all form in SingleChildScrollView:
class TestScreen extends StatefulWidget {
static const routeName = '/test-screen';
#override
_TestScreenState createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
final _form = GlobalKey<FormState>();
Future<void> _saveForm() async {
final isValid = _form.currentState.validate();
if (!isValid) {
return;
}
}
Widget _buildContainer(Widget child) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(10),
),
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(10),
height: 200,
width: 300,
child: child,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Test',
style: Theme.of(context).textTheme.title,
),
automaticallyImplyLeading: false,
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Form(
key: _form,
child: Column(
children: <Widget>[
TextFormField(
initialValue: '',
decoration: InputDecoration(labelText: 'Name'),
validator: (value) {
if (value.isEmpty) {
return 'Insert something';
}
return null;
},
),
TextFormField(
initialValue: '',
decoration: InputDecoration(labelText: 'Number'),
keyboardType: TextInputType.number,
validator: (value) {
if (int.tryParse(value) == null) {
return 'Insert a number';
}
return null;
},
),
SizedBox(height: 20),
_buildContainer(
ListView(
children: <Widget>[],
),
),
Text(
'Text-Test1',
textAlign: TextAlign.center,
),
Slider(
value: 1,
divisions: 3,
min: 0.0,
max: 3.0,
label: 'Test',
onChanged: (newValue) {},
),
SizedBox(
height: 20,
),
Text(
'Text-Test2',
textAlign: TextAlign.center,
),
Slider(
value: 3,
divisions: 4,
min: 0.0,
max: 4.0,
label: 'Nothing2',
onChanged: (newValue) {},
),
SizedBox(
height: 20,
),
Row(
children: <Widget>[
Text('RandomLabel'),
Spacer(),
Container(
width: 100,
child: TextFormField(
initialValue: '',
keyboardType: TextInputType.number,
validator: (value) {
if (int.tryParse(value) == null) {
return 'Insert a number';
}
return null;
},
),
),
],
),
SizedBox(
height: 20,
),
Text(
'Test 2',
textAlign: TextAlign.center,
),
_buildContainer(
ListView.builder(
itemCount: 0,
itemBuilder: (ctx, index) {
return ListTile(
title: Text('hello'),
subtitle: Text('world'),
);
},
),
),
TextFormField(
initialValue: '',
minLines: 4,
maxLines: 4,
decoration: InputDecoration(labelText: 'Text'),
validator: (value) {
if (value.isEmpty) {
return 'Insert something';
}
return null;
},
),
FlatButton.icon(
icon: Icon(Icons.check),
label: Text('Done'),
onPressed: () {
_saveForm();
},
),
],
),
),
),
),
);
}
}