Im trying to create a trainings app for myself.
I have created a app that can add more textfields everytime a button is pushed.
The problem im having is all the fields have the same value and i cant enter two diffrent
I have tried a couple of things and the closest i got was when i could do diffrent values but not save it.
My code is the following:
class EventEditingPage extends StatefulWidget {
final Event? event;
const EventEditingPage({Key? key, this.event}) : super(key: key);
#override
_EventEditingPageState createState() => _EventEditingPageState();
}
class _EventEditingPageState extends State<EventEditingPage> {
final _formKey = GlobalKey<FormState>();
final setsController = TextEditingController();
final repsController = TextEditingController();
final loadController = TextEditingController();
late DropdownButton Exercise;
#override
void initState() {
super.initState();
final event = widget.event!;
exercise = event.Exercise;
setsController.text = event.Sets;
repsController.text = event.Reps;
loadController.text = event.Load;
}
}
#override
void dispose() {
setsController.dispose();
repsController.dispose();
loadController.dispose();
super.dispose();
}
int numberOfTextFields = 1;
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
leading: CloseButton(),
actions: buildEditingActions(),
backgroundColor: Colors.red,
),
body: SingleChildScrollView(
padding: EdgeInsets.all(12),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("text", style: TextStyle(fontSize: 24)),
SizedBox(height: 12),
Text("\ntext", style: TextStyle(fontSize: 24)),
for(int i = 0 ; i < numberOfTextFields ; i++)
buildExerciseComplete(),
buildAddMore(),
],
),
),
),
);
List<Widget> buildEditingActions() => [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
primary: Colors.transparent, shadowColor: Colors.transparent),
onPressed: saveForm,
icon: Icon(Icons.done),
label: Text('Save'),
)
];
Widget buildExerciseComplete() => Container(
padding: EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: Color.fromARGB(255, 255, 200, 200),
borderRadius: BorderRadius.circular(5.0),
border: Border.all(
width: 2.0,
),
),
height: 50,
child: Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
Expanded(child: buildExercise()),
Expanded(child: buildSets()),
Expanded(child: buildReps()),
Expanded(child: buildLoad()),
],
),
),
],
),
);
Widget buildSets() => TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: TextStyle(fontSize: 15),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Sets',
),
onFieldSubmitted: (_) => saveForm,
validator: (sets) =>
sets != null && sets.isEmpty ? 'Sets cannot be empty' : null,
controller: setsController,
);
Widget buildReps() => TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: TextStyle(fontSize: 15),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Reps',
),
onFieldSubmitted: (_) => saveForm,
validator: (reps) =>
reps != null && reps.isEmpty ? 'Reps cannot be empty' : null,
controller: repsController,
);
Widget buildLoad() => TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: TextStyle(fontSize: 15),
decoration: InputDecoration(
labelText: 'Load',
border: OutlineInputBorder(),
),
onFieldSubmitted: (_) => saveForm,
validator: (load) =>
load != null && load.isEmpty ? 'Load cannot be empty' : null,
controller: loadController,
);
String exercise = 'Squat';
Widget buildExercise() => DropdownButton<String>(
value: exercise,
icon: const Icon(Icons.arrow_drop_down),
elevation: 16,
style: const TextStyle(color: Colors.black),
onChanged: (String? newValue) {
setState(() {
exercise = newValue!;
});
},
items: <String>['Squat', 'Deadlift', 'Bench']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
Widget buildAddMore() => ElevatedButton(
onPressed: () {
setState((){
numberOfTextFields++;
_textEditingControllers.add(TextEditingController());
});
},
child: Text('Add new exercise'),
);
Future saveForm() async {
final isValid = _formKey.currentState!.validate();
if (isValid) {
final event = Event(
Exercise: exercise,
Sets: setsController.text,
Reps: repsController.text,
Load: loadController.text);
final isEditing = widget.event != null;
final provider = Provider.of<EventProvider>(context, listen: false);
if (isEditing) {
provider.editEvent(event, widget.event!);
Navigator.of(context).pop();
} else {
provider.addEvent(event);
}
Navigator.of(context).pop();
}
}
}
class Event {
final Exercise;
final String Sets;
final String Reps;
final String Load;
final Color backgroundColor;
const Event({
required this.Sets,
required this.Reps,
required this.Load,
this.backgroundColor = Colors.lightGreen,
});
}
Im pretty the reason has something to do with TextEditingController()
Im hoping that somebody could help me with the solution to my problem :-)
It seems you are building all of the fields with the same controller, so all will have the same value. You should make a list of controllers and add controllers.
Declare variables like this:
final List<TextEditingController> setsController = [];
final List<TextEditingController> repsController = [];
final List<TextEditingController> loadController = [];
Then Create Controller:
createControllers() {
for (var i = 0; i < numberOfTextFields; i++) {
setsController.add(TextEditingController());
repsController.add(TextEditingController());
loadController.add(TextEditingController());
}
}
Use the controller in respective text Field:
controller: repsController[index]
Call the create controller method at initState().
I guess you can handle the value retrieve and dispose of the controllers from here.
I don't know your save logic, so I commented it out.
class _EventEditingPageState extends State<EventEditingPage> {
final _formKey = GlobalKey<FormState>();
final List<TextEditingController> setsController = [];
final List<TextEditingController> repsController = [];
final List<TextEditingController> loadController = [];
// late DropdownButton Exercise;
List<String> exceriseItems = ['Squat', 'Deadlift', 'Bench'];
List<String> selectedExercise = [];
int numberOfTextFields = 1;
#override
void initState() {
super.initState();
final event = widget.event!;
createControllers();
selectedExercise[0] = event.Exercise != "" ? event.Exercise : exceriseItems[0];
setsController[0].text = event.Sets;
repsController[0].text = event.Reps;
loadController[0].text = event.Load;
}
#override
void dispose() {
setsController.forEach((TextEditingController element) {
element.dispose();
});
// Or simply clear the array.
// todo : repsController dispose();
// todo : loadController dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
leading: const CloseButton(),
actions: buildEditingActions(),
backgroundColor: Colors.red,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(12),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text("text", style: TextStyle(fontSize: 24)),
const SizedBox(height: 12),
const Text("\ntext", style: TextStyle(fontSize: 24)),
for (int i = 0; i < numberOfTextFields; i++)
buildExerciseComplete(i),
buildAddMore(),
],
),
),
),
);
List<Widget> buildEditingActions() => [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
primary: Colors.transparent, shadowColor: Colors.transparent),
onPressed: saveForm,
icon: const Icon(Icons.done),
label: const Text('Save'),
)
];
Widget buildExerciseComplete(int idx) => Container(
padding: const EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 255, 200, 200),
borderRadius: BorderRadius.circular(5.0),
border: Border.all(
width: 2.0,
),
),
height: 50,
child: Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
Expanded(child: buildExercise(idx)),
Expanded(child: buildSets(idx)),
Expanded(child: buildReps(idx)),
Expanded(child: buildLoad(idx)),
],
),
),
],
),
);
Widget buildSets(int idx) => TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: const TextStyle(fontSize: 15),
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Sets',
),
onFieldSubmitted: (_) => saveForm,
validator: (sets) =>
sets != null && sets.isEmpty ? 'Sets cannot be empty' : null,
controller: setsController[idx],
);
Widget buildReps(int idx) => TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: const TextStyle(fontSize: 15),
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Reps',
),
onFieldSubmitted: (_) => saveForm,
validator: (reps) =>
reps != null && reps.isEmpty ? 'Reps cannot be empty' : null,
controller: repsController[idx],
);
Widget buildLoad(int idx) => TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: const TextStyle(fontSize: 15),
decoration: const InputDecoration(
labelText: 'Load',
border: const OutlineInputBorder(),
),
onFieldSubmitted: (_) => saveForm,
validator: (load) =>
load != null && load.isEmpty ? 'Load cannot be empty' : null,
controller: loadController[idx],
);
Widget buildExercise(int idx) => DropdownButton<String>(
value: selectedExercise[idx],
icon: const Icon(Icons.arrow_drop_down),
elevation: 16,
style: const TextStyle(color: Colors.black),
onChanged: (String? newValue) {
setState(() {
selectedExercise[idx] = newValue!;
});
},
items: exceriseItems
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
Widget buildAddMore() => ElevatedButton(
onPressed: () {
setState(() {
numberOfTextFields++;
createControllers();
});
},
child: const Text('Add new exercise'),
);
createControllers() {
for (var i = 0; i < numberOfTextFields; i++) {
setsController.add(TextEditingController());
repsController.add(TextEditingController());
loadController.add(TextEditingController());
}
selectedExercise.add(exceriseItems[0]);
}
Future saveForm() async {
final isValid = _formKey.currentState!.validate();
if (isValid) {
// final event = Event(
// Exercise: exercise,
// Sets: setsController.text,
// Reps: repsController.text,
// Load: loadController.text);
final isEditing = widget.event != null;
// final provider = Provider.of<EventProvider>(context, listen: false);
// if (isEditing) {
// provider.editEvent(event, widget.event!);
//
// Navigator.of(context).pop();
// } else {
// provider.addEvent(event);
// }
//
// Navigator.of(context).pop();
}
}
}
Related
Below is the AddNewDocument.dart file. Whenever I add any document it should show new push notification to the user and should navigate to document details page. Is there any method to implement this?
import 'package:AtDocHUB/Controller/DocumentController.dart';
import 'package:AtDocHUB/Model/Document.dart';
import 'package:AtDocHUB/View/Document/DocumentPage.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:form_field_validator/form_field_validator.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:jiffy/jiffy.dart';
DateTime now = DateTime.now();
String formattedDate = DateFormat("yyyy-MM-dd").format(now);
//import 'package:flutter_text_form_field/flutter_text_form_field.dart';
class AddNewDocument extends StatefulWidget {
#override
State createState() {
return _AddNewDocumentState();
}
}
class _AddNewDocumentState extends State<AddNewDocument> {
late final String date1;
late String datainput;
final List<String> items1 = ["Open", "Closed", "Cancel", "Submitted"];
String? selectedItem;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController docTitleController = TextEditingController();
TextEditingController tokenNoController = TextEditingController();
TextEditingController partyNameController = TextEditingController();
TextEditingController startDateController = TextEditingController();
TextEditingController endDateController = TextEditingController();
late String docType = docTypeController.text;
final List<String> items = [
"Residential",
"Commercial",
];
String? selectedValue;
List<DropdownMenuItem<String>> _addDividersAfterItems(List<String> items) {
List<DropdownMenuItem<String>> _menuItems = [];
for (var item in items) {
_menuItems.addAll(
[
DropdownMenuItem<String>(
value: item,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
item,
style: const TextStyle(
fontSize: 14,
),
),
),
),
//If it's last item, we will not add Divider after it.
if (item != items.last)
const DropdownMenuItem<String>(
enabled: false,
child: Divider(),
),
],
);
}
return _menuItems;
}
List<int> _getDividersIndexes() {
List<int> _dividersIndexes = [];
for (var i = 0; i < (items.length * 2) - 1; i++) {
//Dividers indexes will be the odd indexes
if (i.isOdd) {
_dividersIndexes.add(i);
}
}
return _dividersIndexes;
}
#override
void dispose() {
docStatusController.dispose();
docTypeController.dispose();
super.dispose();
}
#override
Future<Document>? _futureDocument;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Color.fromARGB(255, 3, 87, 156),
title: Text('Add New Document'),
leading: IconButton(
icon: BackButtonIcon(),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => DocumentPage()))),
),
body:
//Container(
SafeArea(
child: SingleChildScrollView(
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(10),
child: (_futureDocument == null)
? buildColumn()
: buildFutureBuilder(),
),
),
),
);
}
Form buildColumn() {
return Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 10,
),
TextFormField(
minLines: 1,
maxLines: 3,
textCapitalization: TextCapitalization.words,
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp("[ ',-/ a-z A-Z á-ú Á-Ú 0-9]")),
],
controller: docTitleController,
style: TextStyle(fontSize: 12),
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
errorStyle: const TextStyle(fontSize: 0.05),
border: OutlineInputBorder(),
hintStyle: TextStyle(fontSize: 12),
labelStyle: TextStyle(
fontSize: 12,
),
labelText: 'Document Title',
hintText: 'Document title required'),
// validator: ,
validator:
MultiValidator([RequiredValidator(errorText: 'Required*')]),
),
SizedBox(
height: 20,
),
Wrap(
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.4,
child: TextFormField(
style: TextStyle(fontSize: 12),
minLines: 1,
maxLines: 2,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(14)
],
controller: tokenNoController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
errorStyle: const TextStyle(fontSize: 0.05),
counterText: "",
border: OutlineInputBorder(),
hintStyle: TextStyle(fontSize: 12),
labelStyle: TextStyle(
fontSize: 12,
),
labelText: 'Token No',
hintText: 'Token no required'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter token No';
} else if (value.length < 14) {
return 'Please enter 14 digits number';
}
return null;
},
),
),
SizedBox(
width: 15,
),
SizedBox(
height: 20,
),
SizedBox(
width: MediaQuery.of(context).size.width,
child: TextFormField(
minLines: 1,
maxLines: 4,
inputFormatters: [
LengthLimitingTextInputFormatter(50),
FilteringTextInputFormatter.allow(RegExp("[a-z A-Z]")),
],
controller: partyNameController,
keyboardType: TextInputType.name,
decoration: const InputDecoration(
errorStyle: const TextStyle(fontSize: 0.05),
border: OutlineInputBorder(),
hintStyle: TextStyle(fontSize: 12),
labelStyle: TextStyle(
fontSize: 12,
),
labelText: 'Party Name',
hintText: 'Party name required'),
validator:
MultiValidator([RequiredValidator(errorText: 'Required*')]),
),
),
SizedBox(
height: 20,
),
SizedBox(
height: 20,
),
Row(
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.4,
child: TextFormField(
// autovalidateMode: AutovalidateMode.onUserInteraction,
//FilteringTextInputFormatter.allow(RegExp("[- 0-9]")),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp("[- 0-9]")),
LengthLimitingTextInputFormatter(10)
],
controller: startDateController,
keyboardType: TextInputType.datetime,
decoration: const InputDecoration(
errorStyle: const TextStyle(fontSize: 0.05),
prefixIcon: Icon(Icons.calendar_month),
border: OutlineInputBorder(),
hintStyle: TextStyle(fontSize: 12),
labelStyle: TextStyle(
fontSize: 12,
),
labelText: 'Start Date',
hintText: 'yyyy-MM-dd',
),
onTap: () async {
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(
1991), //DateTime.now() - not to allow to choose before today.
lastDate: DateTime(2101),
// onConfirm:widget.onChanged,
).then((pickedDate) {
if (pickedDate != null) {
// print(
// pickedDate); //pickedDate output format => 2021-03-10 00:00:00.000
String formattedDate =
DateFormat('yyyy-MM-dd').format(pickedDate);
print(formattedDate);
setState(() {
startDateController.text = formattedDate;
//set output date to TextField value.
});
print(startDateController.text);
} else {
print("Date is not selected");
}
});
final int dur = int.parse(durationController.text);
var stDate = DateTime.parse(startDateController.text);
var jiffy = Jiffy(stDate).add(
months: dur,
days: -1,
// days: 1095,
);
DateTime d = jiffy.dateTime;
String s = jiffy.format('yyyy-MM-dd');
setState(() {
endDateController.text = s.toString();
});
},
validator: MultiValidator(
[RequiredValidator(errorText: 'Required*')]),
),
),
SizedBox(
width: 13,
),
SizedBox(
// height: MediaQuery.of(context).size.height * 0.06,
width: MediaQuery.of(context).size.width * 0.5,
child: TextFormField(
// maxLength: 8,
// autovalidateMode: AutovalidateMode.onUserInteraction,
autofocus: false,
controller: endDateController,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp("[- 0-9]")),
LengthLimitingTextInputFormatter(10)
],
keyboardType: TextInputType.datetime,
decoration: const InputDecoration(
errorStyle: const TextStyle(fontSize: 0.05),
// prefixIcon: Icon(Icons.calendar_month),
counterText: "",
border: OutlineInputBorder(),
hintStyle: TextStyle(fontSize: 12),
labelStyle: TextStyle(
fontSize: 12,
),
labelText: 'End Date',
hintText: ' yyyy-MM-dd'),
onTap: () async {},
validator: MultiValidator(
[RequiredValidator(errorText: 'Required*')]),
),
),
],
),
SizedBox(
height: 20,
),
SizedBox(
// height: MediaQuery.of(context).size.height * 0.06,
width: MediaQuery.of(context).size.width,
child: TextFormField(
minLines: 1,
maxLines: 4,
// autovalidateMode: AutovalidateMode.onUserInteraction,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp("[a-z A-Z 0-9]")),
// FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(50)
],
controller: rentDescController,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
errorStyle: const TextStyle(fontSize: 0.05),
border: OutlineInputBorder(),
hintStyle: TextStyle(fontSize: 12),
labelStyle: TextStyle(
fontSize: 12,
),
labelText: 'Rent Description',
hintText: 'Rent Description required'),
// validator:
// MultiValidator([RequiredValidator(errorText: 'Required*')]),
),
),
SizedBox(
height: 20,
),
Container(
//alignment: Alignment.center,
height: 35,
width: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Color.fromARGB(255, 3, 89, 168),
),
child: ElevatedButton(
onPressed: () {
formKey.currentState?.validate();
final isValidForm = formKey.currentState!.validate();
if (isValidForm) {
//formKey.currentState?.validate();
// final int docId = int.parse(docIdController.text).toInt();
final String docTitle = docTitleController.text;
final int tokenNo =
int.parse(tokenNoController.text).toInt();
//final String tokenNo = tokenNoController.text;
final String partyName = partyNameController.text;
final String startDate = startDateController.text;
final String endDate = endDateController.text;
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => DocumentPage()));
setState(() {
_futureDocument = createDocument(
// docId,
docTitle,
tokenNo,
startDate,
endDate,
// createdAt,
);
});
// }
}
},
child: const Text("Save"),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Color.fromARGB(255, 3, 89, 168))),
))
],
),
);
//])
//);
}
FutureBuilder<Document> buildFutureBuilder() {
return FutureBuilder<Document>(
future: _futureDocument,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.docTitle);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
);
}
}
Below is the main.dart file where I have initialised App Id and other required things.Push Notification is coming in app properly and when I tap on the notification it opens the application. But i wants to show push notification when the new document is added to the app and when user tap on that particular notification it should navigate user to the document details page.I have also attached AddNewDocumentFile.dart.
import 'package:flutter/material.dart';
import 'package:onesignal_flutter/onesignal_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'View/LoginPage.dart';
import 'View/homePageAdmin.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await SharedPrefService.init();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class SharedPrefService {
static late SharedPreferences pref;
static Future<void> init() async {
pref = await SharedPreferences.getInstance();
var usrEmail = pref.getString('email');
}
}
class _MyAppState extends State<MyApp> {
// This widget is the root of your application.
#override
void initState() {
// TODO: implement initState
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
OneSignal.shared.setAppId('e89acaa4-5388-4e3a-bd69-44d197bdcbd7');
OneSignal.shared
.promptUserForPushNotificationPermission()
.then((accepted) {});
}
#override
Widget build(BuildContext context) {
var usrEmail = SharedPrefService.pref.getString('email');
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: usrEmail == null ? LoginPage() : homePageAdmin(),
);
}
}
You just need to call the OneSignal.shared.postNotification(notification); after your createDocument function completes.
import 'package:flutter/material.dart';
import 'package:sumanthk07/utilities/routes.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
#override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _formkey = GlobalKey<FormState>();
// ignore: avoid_types_as_parameter_names, non_constant_identifier_names
moveToHome(BuildContext) async{
Navigator.pushNamed(context, MyRoutes.homeRoute);
}
#override
Widget build(BuildContext context) {
return Material(
color: Colors.white,
child: SingleChildScrollView(
child: Form(
key: _formkey,
child: Column(
children: [
Image.asset("assets/images/login.png", fit: BoxFit.cover),
const SizedBox(
height: 20.0,
),
const Text(
'Welcome',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(
height: 20.0,
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 16.0, horizontal: 32.0),
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(
hintText: "Enter User name", labelText: "Username "),
initialValue: "",
validator: (String? value) {
if (value !=null && value.isEmpty ) {
return "User name cannot be empty";
}
return null;
},
onChanged: (value) {
setState(() {});
},
),
TextFormField(
obscureText: true,
decoration: const InputDecoration(
hintText: "Enter password", labelText: "Password "),
initialValue: "",
validator: (String? value) {
if (value !=null && value.isEmpty ) {
return "Password name cannot be empty";
}
return null;
},
),
const SizedBox(
height: 20.0,
),
InkWell(
onTap: () => moveToHome(context),
child: AnimatedContainer(
duration: const Duration(seconds: 1),
height: 40,
width: 80,
alignment: Alignment.center,
child: const Text("Login",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
)),
decoration: BoxDecoration(
color: Colors.red,
// ignore: unnecessary_const
borderRadius: BorderRadius.circular(20)),
),
)
// ElevatedButton(
// child: const Text("Login"),
// style: TextButton.styleFrom(),
// onPressed: () {
// // ignore: unused_local_variable
// var myRoutes = MyRoutes;
// Navigator.pushNamed(context, MyRoutes.homeRoute);
// },
// )
],
),
)
],
),
),
),
);
}
BorderRadius newMethod() => BorderRadius.circular(20);
}
Hi All, I'm a beginner to flutter and I'm trying to add validator to widget but I'm not getting the validation when I run the application.
I searched and tried the ways to do it but I didn't get the desired outcome.
Can you guys look into my code and suggest the right way.
no errors found but validation is not working.
First assign TextEditingController to your both fields.
final TextEditingController _controllerUserName = TextEditingController();
final TextEditingController _controllerPassword = TextEditingController();
And also assign autovalidateMode to your text field so you can validate at user input like this. It's not necessary it's optional but you can add it to validate your field on input field changes. Although you can validate your form at submission time.
TextFormField(
decoration: const InputDecoration(
hintText: "Enter User name", labelText: "Username "),
initialValue: "",
validator: (String? value) {
if (value !=null && value.isEmpty ) {
return "User name cannot be empty";
}
return null;
},
onChanged: (value) {
setState(() {});
},
autovalidate : AutovalidateMode.onUserInteraction,
controller:_controllerUserName
),
And also you have not validate your form at submission time. try this
moveToHome(BuildContext) async{
if (_formkey.currentState.validate()) {
Navigator.pushNamed(context, MyRoutes.homeRoute);
}
}
I am design a screen in which I need to opening a drop down list when user click on textfield till now I am using CupertinoActionSheetAction sheet which is correctly working now I need to replace it with DropdownButton but when I am doing it is not displaying on screen I don't know why.
Here is my code focus on line number 136 (function for CupertinoActionSheetAction is working code) and line 138 where (function for drop not working code) according to me may be it due to build context but I m not sure how to use 'build context' in this context can any one help me out to fix that.
Thanks in advance, And any suggestion for making my code more reusable will be helpful as well :)
import 'dart:ffi';
import 'package:fleet_management/AppContants/Contants.dart';
import 'package:fleet_management/Controllers/NewFuelExpenseController.dart';
import 'package:fleet_management/Models/NewFuelExpenseModel.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class AddFuelView extends StatefulWidget {
const AddFuelView({Key? key}) : super(key: key);
#override
_AddFuelView createState() {
return _AddFuelView();
}
}
// MyTripHistoryView
class _AddFuelView extends State<AddFuelView> {
NewFuelExpenseModel fuelExpense =
NewFuelExpenseController().getNewFuelExpense();
DateTime _selectedDate = DateTime.now();
String dropdownvalue = 'Item 1';
// List of items in our dropdown menu
var items = [
'Item 1',
'Item 2',
'Item 3',
'Item 4',
'Item 5',
];
TextStyle _titleTextStyle = const TextStyle();
TextStyle _valueTextStyle = const TextStyle();
final GlobalKey newFuelExpenseFormField = GlobalKey<FormState>();
final TextEditingController _dateEditingController = TextEditingController();
final TextEditingController _priceTextEditingController =
TextEditingController();
final TextEditingController _gallonsTextEditingController =
TextEditingController();
final TextEditingController _totalTextEditingController =
TextEditingController();
final TextEditingController _odometerTextEditingController =
TextEditingController();
final TextEditingController _fuelTypeTextEditingController =
TextEditingController();
final TextEditingController _vendorTextEditingController =
TextEditingController();
#override
void initState() {
super.initState();
_titleTextStyle =
const TextStyle(fontSize: 16, fontWeight: FontWeight.w600);
_valueTextStyle = const TextStyle(
fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black38);
_dateEditingController.text =
"${_selectedDate.day}-${_selectedDate.month}-${_selectedDate.year}";
}
#override
Widget build(BuildContext context) {
var scaffold = Scaffold(
appBar: AppBar(
title: Text(StringsConstants.newFuelExpense),
backgroundColor: AppColorsConstants.primaryColor,
actions: <Widget>[
FlatButton(
textColor: Colors.white,
onPressed: () {
print("SAVE Button Clicked");
},
child: Text("Save", style: _titleTextStyle),
),
],
),
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Form(
key: newFuelExpenseFormField,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
showDateRow(context),
addTextFieldInput(
StringsConstants.priceGallon,
"\u{20B9}0.00",
false,
_priceTextEditingController,
TextInputType.datetime,
null,
null),
addTextFieldInput(
StringsConstants.gallons,
"",
false,
_gallonsTextEditingController,
TextInputType.number,
null,
null),
addTextFieldInput(
StringsConstants.total,
"\u{20B9}0.00",
false,
_totalTextEditingController,
TextInputType.number,
null,
null),
addTextFieldInput(
StringsConstants.odometer,
"",
false,
_odometerTextEditingController,
TextInputType.number,
null,
null),
// show action sheet
addTextFieldInput(
StringsConstants.fuelType,
"",
true,
_fuelTypeTextEditingController,
TextInputType.none,
null, () {
// Working. - >>>> HERE <<<<<
showActionSheet(context);
// Not Working >>>> HERE <<<<< Uncomment following function before
// showDropDown();
}),
addTextFieldInput(
StringsConstants.vendor,
"",
false,
_vendorTextEditingController,
TextInputType.text,
null,
null),
const SizedBox(
height: 50,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
print("Submit button pressed!");
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24),
primary: AppColorsConstants.primaryColor,
textStyle:
const TextStyle(fontWeight: FontWeight.bold),
),
child: Text(StringsConstants.submit)),
ElevatedButton(
onPressed: () {
print("Cancel button pressed!");
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24),
primary: AppColorsConstants.primaryColor,
textStyle:
const TextStyle(fontWeight: FontWeight.bold),
),
child: Text(StringsConstants.cancel))
],
)
],
),
),
),
),
),
);
return scaffold;
}
void showDropDown() {
DropdownButton<String>(
items: <String>['A', 'B', 'C', 'D'].map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (_) {},
);
}
Future<dynamic> showActionSheet(BuildContext context) {
return showCupertinoModalPopup(
context: context,
builder: (BuildContext context) => CupertinoActionSheet(
title: const Text('Choose Options'),
actions: <Widget>[
CupertinoActionSheetAction(
child: const Text('Oil'),
onPressed: () {
_fuelTypeTextEditingController.text = "Oil";
Navigator.pop(context, 'Oil');
},
),
CupertinoActionSheetAction(
child: const Text('Petrol'),
onPressed: () {
_fuelTypeTextEditingController.text = "Petrol";
Navigator.pop(context, 'Petrol');
},
),
CupertinoActionSheetAction(
child: const Text('diesel'),
onPressed: () {
_fuelTypeTextEditingController.text = "diesel";
Navigator.pop(context, 'diesel');
},
)
],
cancelButton: CupertinoActionSheetAction(
child: const Text('Cancel'),
isDefaultAction: true,
onPressed: () {
Navigator.pop(context, 'Cancel');
},
)),
);
}
Container addTextFieldInput(
String title,
String initialValue,
bool isEditable,
TextEditingController textfieldController,
TextInputType keyboardType,
FormFieldValidator<String>? validator,
GestureTapCallback? tabCallback,
) {
return Container(
padding: const EdgeInsets.fromLTRB(0, 16, 0, 8),
child: SizedBox(
child: TextFormField(
onTap: tabCallback,
keyboardType: keyboardType,
cursorHeight: 16,
cursorWidth: 1.4,
readOnly: isEditable,
controller: textfieldController,
validator: validator,
decoration: InputDecoration(
isDense: true,
floatingLabelStyle:
TextStyle(color: AppColorsConstants.primaryColor),
labelText: title,
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: AppColorsConstants.primaryColor, width: 1.5),
),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black38, width: 1.5),
),
border: const OutlineInputBorder()),
style: const TextStyle(fontSize: 18),
cursorColor: Colors.black38,
showCursor: true,
autofocus: true,
),
),
);
}
Container showDateRow(BuildContext context) {
return Container(
child: TextFormField(
onTap: () => {_selectDate(context)},
cursorHeight: 16,
cursorWidth: 1.4,
readOnly: true,
enableInteractiveSelection: false,
controller: _dateEditingController,
decoration: InputDecoration(
isDense: true,
labelStyle: const TextStyle(color: Colors.orangeAccent),
labelText: StringsConstants.date,
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.orangeAccent, width: 1.5),
),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black38, width: 1.5),
),
border: const OutlineInputBorder()),
style: const TextStyle(fontSize: 18),
cursorColor: Colors.black38,
showCursor: true,
autofocus: true,
),
);
}
Future<void> _selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: _selectedDate,
firstDate: DateTime(1990),
lastDate: DateTime.now());
if (picked != null && picked != _selectedDate) {
setState(() {
_selectedDate = picked;
this._dateEditingController.text =
"${_selectedDate.day}-${_selectedDate.month}-${_selectedDate.year}";
});
}
}
}
For DropDown in flutter you need
an initail value,
a list to be shown just
String selectedvalue='myvalue';
/// be sure you add the variable value in the 0 index of you list other wise it will thrown error or exception..
List<String> myList=[
'myvalue',/// the value is same as variable value
'othe value', ....
];
DropDownButton(
onChanged: (resultValue){
/// you can get selected value from here
},
items: myList.map((e){
retrun DropdownMenuItem(
value: e,
child: Text(e));
}).toList(),
value: selectedvalue,
),
You can use DropdownButtonFormField in place of TextFormField and can make some style related changes per your requirement.
class MyWidget extends StatelessWidget {
String selectedValue = "USA";
List<DropdownMenuItem<String>> get dropdownItems{
List<DropdownMenuItem<String>> menuItems = [
DropdownMenuItem(child: Text("USA"),value: "USA"),
DropdownMenuItem(child: Text("Canada"),value: "Canada"),
DropdownMenuItem(child: Text("Brazil"),value: "Brazil"),
DropdownMenuItem(child: Text("England"),value: "England"),
];
return menuItems;
}
#override
Widget build(BuildContext context) {
return DropdownButtonFormField(
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 2),
borderRadius: BorderRadius.circular(20),
),
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 2),
borderRadius: BorderRadius.circular(20),
),
filled: true,
fillColor: Colors.blueAccent,
),
dropdownColor: Colors.blueAccent,
value: selectedValue,
onChanged: (String? newValue) {
selectedValue = newValue!;
},
items: dropdownItems);
}
}
I'm opening up a form to enter the details and close it upon filling everything and submitting it, but only the trailing icon is getting changed, also I'm trying to set the changes in the onExpansionChanged but not reflecting in the UI.
Updated Code
Scaffold(
backgroundColor: Colors.grey.shade200,
appBar: AppBar(
title: TextButton(
child: Text(count.toString()),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (_) => RegisterLetterBookedListScreen()));
},
),
backgroundColor: ColorConstants.kPrimaryColor,
elevation: 0,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
MotionToast.success(
title: 'Amount to be Collected',
titleStyle: TextStyle(fontWeight: FontWeight.bold),
description: '\u{20B9} $amountToBeCollected',
descriptionStyle: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
layoutOrientation: ORIENTATION.LTR,
animationType: ANIMATION.FROM_LEFT,
width: 300,
).show(context);
},
child: Text(
'\u{20B9} $amountToBeCollected',
style: TextStyle(fontSize: 20),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//Article Details
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//Sender Details
ExpansionTile(
key: GlobalKey(),
trailing: (senderExpansion == false)
? Icon(
MdiIcons.toggleSwitchOffOutline,
size: 40,
color: Colors.black54,
)
: Icon(MdiIcons.toggleSwitchOutline, size: 40, color: Colors.red),
onExpansionChanged: (value) {
senderExpansion = value;
},
maintainState: true,
initiallyExpanded: senderExpansion,
title: Text(
'Sender Details',
style: TextStyle(
fontWeight: FontWeight.bold,
color: senderHeadingColor,
fontSize: 20,
letterSpacing: 1),
),
children: [
Column(
children: [
CInputForm(
readOnly: false,
iconData: Icons.person,
labelText: 'Name *',
controller: senderNameController,
textType: TextInputType.text,
typeValue: 'Name',
focusNode: senderNameFocusNode,
),
CInputForm(
readOnly: false,
iconData: MdiIcons.home,
labelText: 'Address *',
controller: senderAddressController,
textType: TextInputType.multiline,
typeValue: 'Address',
focusNode: senderAddressFocusNode,
),
Card(
elevation: 0,
child: TypeAheadFormField(
textFieldConfiguration: TextFieldConfiguration(
style: TextStyle(color: Colors.blueGrey),
controller: senderPinCodeCityController,
autofocus: false,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.location_on_outlined,
color: Colors.blueGrey,
),
fillColor: Colors.white,
filled: true,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
labelText: 'Pincode/Office Name *',
labelStyle: TextStyle(
color: ColorConstants.kAmberAccentColor),
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white)))),
onSuggestionSelected:
(Map<String, String> suggestion) async {
senderPinCodeCityController.text = suggestion['pinCode']!;
senderCityController.text = suggestion['city']!;
senderStateController.text = suggestion['state']!;
},
itemBuilder: (context, Map<String, String> suggestion) {
return ListTile(
title: Text(suggestion['officeName']! +
", " +
suggestion['pinCode']!),
);
},
suggestionsCallback: (pattern) async {
return await FetchPin.getSuggestions(pattern);
},
validator: (value) {
if (value!.isEmpty) {
issPin = false;
isLoading = false;
}
},
),
),
Visibility(
visible: issPin == false ? senderPinCodeCityController.text.isEmpty ? true : false : false,
child: Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(left: 17.0),
child: Text('Select a Pincode/Office name', style: TextStyle(fontSize: 10, color: ColorConstants.kPrimaryColor),),
)),
),
Visibility(
visible:
senderPinCodeCityController.text.isEmpty ? false : true,
child: CInputForm(
readOnly: true,
iconData: Icons.location_city,
labelText: 'City',
controller: senderCityController,
textType: TextInputType.text,
typeValue: 'City',
focusNode: senderPinCodeFocusNode,
),
),
Visibility(
visible:
senderPinCodeCityController.text.isEmpty ? false : true,
child: CInputForm(
readOnly: true,
iconData: Icons.location_city,
labelText: 'State',
controller: senderStateController,
textType: TextInputType.text,
typeValue: 'City',
focusNode: senderPinCodeFocusNode,
),
),
CInputForm(
readOnly: false,
iconData: MdiIcons.cellphone,
labelText: 'Mobile Number',
controller: senderMobileNumberController,
textType: TextInputType.number,
typeValue: 'MobileNumber',
focusNode: senderMobileFocusNode,
),
CInputForm(
readOnly: false,
iconData: MdiIcons.email,
labelText: 'Email',
controller: senderEmailController,
textType: TextInputType.emailAddress,
typeValue: 'Email',
focusNode: senderEmailFocusNode,
),
],
)
],
),
//Addressee Details
ExpansionTile(
trailing: (addresseeExpansion == false)
? Icon(
MdiIcons.toggleSwitchOffOutline,
size: 40,
color: Colors.black54,
)
: Icon(MdiIcons.toggleSwitchOutline, size: 40, color: Colors.red),
onExpansionChanged: (value) {
addresseeExpansion = value;
},
maintainState: true,
title: Text('Addressee Details',
style: TextStyle(
fontWeight: FontWeight.bold,
color: addresseeHeadingColor,
fontSize: 20,
letterSpacing: 1)),
children: [
Column(
children: [
CInputForm(
readOnly: false,
iconData: Icons.person,
labelText: 'Name *',
controller: addresseeNameController,
textType: TextInputType.text,
typeValue: 'Name',
focusNode: addresseeNameFocusNode,
),
CInputForm(
readOnly: false,
iconData: MdiIcons.home,
labelText: 'Address *',
controller: addresseeAddressController,
textType: TextInputType.multiline,
typeValue: 'Address',
focusNode: addresseeAddressFocusNode,
),
Card(
elevation: 0,
child: TypeAheadFormField(
textFieldConfiguration: TextFieldConfiguration(
style: TextStyle(color: Colors.blueGrey),
controller: addresseePinCodeCityController,
autofocus: false,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.location_on_outlined,
color: Colors.blueGrey,
),
fillColor: Colors.white,
filled: true,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
labelText: 'Pincode/Office Name *',
labelStyle: TextStyle(
color: ColorConstants.kAmberAccentColor),
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white)))),
onSuggestionSelected:
(Map<String, String> suggestion) async {
addresseePinCodeCityController.text =
suggestion['pinCode']!;
addresseeCityController.text = suggestion['city']!;
addresseeStateController.text = suggestion['state']!;
},
itemBuilder: (context, Map<String, String> suggestion) {
return ListTile(
title: Text(suggestion['officeName']! +
", " +
suggestion['pinCode']!),
);
},
suggestionsCallback: (pattern) async {
return await FetchPin.getSuggestions(pattern);
},
validator: (value) {
if (value!.isEmpty) {
isLoading = false;
isaPin = false;
}
},
),
),
Visibility(
visible: isaPin == false ? addresseePinCodeCityController.text.isEmpty ? true : false : false,
child: Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(left: 17.0),
child: Text('Select a Pincode/Office name', style: TextStyle(fontSize: 10, color: ColorConstants.kPrimaryColor),),
)),
),
Visibility(
visible: addresseePinCodeCityController.text.isEmpty
? false
: true,
child: CInputForm(
readOnly: true,
iconData: Icons.location_city,
labelText: 'City',
controller: addresseeCityController,
textType: TextInputType.text,
typeValue: 'City',
focusNode: addresseePinCodeFocusNode,
),
),
Visibility(
visible: addresseePinCodeCityController.text.isEmpty
? false
: true,
child: CInputForm(
readOnly: true,
iconData: Icons.location_city,
labelText: 'State',
controller: addresseeStateController,
textType: TextInputType.text,
typeValue: 'State',
focusNode: addresseePinCodeFocusNode,
),
),
CInputForm(
readOnly: false,
iconData: MdiIcons.cellphone,
labelText: 'Mobile Number',
controller: addresseeMobileNumberController,
textType: TextInputType.number,
typeValue: 'MobileNumber',
focusNode: addresseeMobileFocusNode,
),
CInputForm(
readOnly: false,
iconData: MdiIcons.email,
labelText: 'Email',
controller: addresseeEmailController,
textType: TextInputType.emailAddress,
typeValue: 'Email',
focusNode: addresseeEmailFocusNode,
),
],
),
],
),
//Submit
Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all<Color>(Colors.white),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(color: Colors.blueGrey)))),
onPressed: () async {
setState(() {
senderExpansion = !senderExpansion;
addresseeExpansion = !addresseeExpansion;
});
final ifPresent = await RegisterLetterTable()
.select()
.ArticleNumber
.equals(articleNumberController.text)
.toMapList();
if (ifPresent.isNotEmpty) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Article already scanned'),
behavior: SnackBarBehavior.floating,
));
} else {
if (articleNumberController.text.isEmpty) {
setState(() {
articleNumberFocusNode.requestFocus();
});
} else if (weightController.text.isEmpty) {
setState(() {
weightFocusNode.requestFocus();
});
} else if (insuranceCheck == true) {
if (insuranceController.text.isEmpty)
insuranceFocusNode.requestFocus();
} else if (valuePayableCheck == true) {
if (valuePayablePostController.text.isEmpty)
valuePayableFocusNode.requestFocus();
}
if (senderNameController.text.isEmpty ||
senderAddressController.text.isEmpty ||
senderPinCodeCityController.text.isEmpty) {
setState(() {
senderExpansion = true;
});
if (addresseeNameController.text.isEmpty ||
addresseeAddressController.text.isEmpty ||
addresseePinCodeCityController.text.isEmpty) {
setState(() {
addresseeExpansion = true;
});
} else
setState(() {
addresseeExpansion = false;
});
} else
senderExpansion = false;
if (addresseeNameController.text.isEmpty ||
addresseeAddressController.text.isEmpty ||
addresseePinCodeCityController.text.isEmpty) {
setState(() {
addresseeExpansion = true;
});
} else
setState(() {
addresseeExpansion = false;
});
if (formGlobalKey.currentState!.validate()) {
formGlobalKey.currentState!.save();
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return ConformationDialog(
articleNumber: articleNumberController.text,
weight: weightController.text,
prepaidAmount: prepaidAmountController.text,
acknowledgement: acknowledgementCheck,
insurance: insuranceCheck == true
? insuranceController.text
: '',
valuePayablePost: valuePayableCheck == true
? valuePayablePostController.text
: '',
amountToBeCollected:
amountToBeCollectedController.text,
senderName: senderNameController.text,
senderAddress: senderAddressController.text,
senderPinCode: senderPinCodeCityController.text,
senderCity: senderCityController.text,
senderState: senderStateController.text,
senderMobileNumber:
senderMobileNumberController.text,
senderEmail: senderEmailController.text,
addresseeName: addresseeNameController.text,
addresseeAddress: addresseeAddressController.text,
addresseePinCode:
addresseePinCodeCityController.text,
addresseeCity: addresseeCityController.text,
addresseeState: addresseeStateController.text,
addresseeMobileNumber:
addresseeMobileNumberController.text,
addresseeEmail: addresseeEmailController.text,
function: () {
Navigator.of(context).pop();
printFunction();
});
});
}
}
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: new Text(
'SUBMIT',
overflow: TextOverflow.ellipsis,
style: TextStyle(color: ColorConstants.kAmberAccentColor),
),
),
),
),
)
],
),
),
))
checkout below code it may help you,
class TestExpanTile extends StatefulWidget {
#override
TestExpanTileState createState() => new TestExpanTileState();
}
class TestExpanTileState extends State<TestExpanTile> {
String senderDet = 'Sender Details ';
bool formOpenFlag = true;
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('ExpansionTile'),
),
body: new ExpansionTile(
key: GlobalKey(),
initiallyExpanded: formOpenFlag,
onExpansionChanged: (val) {
formOpenFlag = val; // update here on change
},
title: new Text(this.senderDet),
backgroundColor: Theme.of(context).accentColor.withOpacity(0.025),
children: <Widget>[
new ListTile(
title: const Text('Your Filling Form goes here'),
onTap: () {},
),
RaisedButton(
onPressed: () {
setState(() {
formOpenFlag = !formOpenFlag;
});
},
child: Text("Close Tile On Submit"),
)
],
),
),
);
}
}
Since your using Multiple ExpansionTiles, as said by #Jai Techie you need a key to control its state, but as per my knowledge we cannot provide the state of the ExpansionTile as the GlobalKey, so in order to acheive it create a new CustomExpansionTile.
import 'package:flutter/material.dart';
class FormExpansionTileCard extends StatefulWidget {
const FormExpansionTileCard({
Key? key,
this.leading,
required this.title,
this.subtitle,
this.onExpansionChanged,
this.children = const <Widget>[],
this.trailing,
this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
this.elevation = 2.0,
this.initialElevation = 0.0,
this.initiallyExpanded = false,
this.initialPadding = EdgeInsets.zero,
this.finalPadding = const EdgeInsets.only(bottom: 6.0),
this.contentPadding,
this.baseColor,
this.expandedColor,
this.expandedTextColor,
this.duration = const Duration(milliseconds: 200),
this.elevationCurve = Curves.easeOut,
this.heightFactorCurve = Curves.easeIn,
this.turnsCurve = Curves.easeIn,
this.colorCurve = Curves.easeIn,
this.paddingCurve = Curves.easeIn,
this.isThreeLine = false,
this.shadowColor = const Color(0xffaaaaaa),
this.animateTrailing = false,
}) : assert(initiallyExpanded != null),
super(key: key);
final bool isThreeLine;
final Widget? leading;
final Widget title;
final Widget? subtitle;
final ValueChanged<bool>? onExpansionChanged;
final List<Widget> children;
final Widget? trailing;
final bool animateTrailing;
final BorderRadiusGeometry borderRadius;
final double elevation;
final double initialElevation;
final Color shadowColor;
final bool initiallyExpanded;
final EdgeInsetsGeometry initialPadding;
final EdgeInsetsGeometry finalPadding;
final EdgeInsetsGeometry? contentPadding;
final Color? baseColor;
final Color? expandedColor;
final Color? expandedTextColor;
final Duration duration;
final Curve elevationCurve;
final Curve heightFactorCurve;
final Curve turnsCurve;
final Curve colorCurve;
final Curve paddingCurve;
#override
FormExpansionTileCardState createState() => FormExpansionTileCardState();
}
class FormExpansionTileCardState extends State<FormExpansionTileCard>
with SingleTickerProviderStateMixin {
static final Animatable<double> _halfTween =
Tween<double>(begin: 0.0, end: 0.5);
final ColorTween _headerColorTween = ColorTween();
final ColorTween _iconColorTween = ColorTween();
final ColorTween _materialColorTween = ColorTween();
late EdgeInsetsTween _edgeInsetsTween;
late Animatable<double> _elevationTween;
late Animatable<double> _heightFactorTween;
late Animatable<double> _turnsTween;
late Animatable<double> _colorTween;
late Animatable<double> _paddingTween;
late AnimationController _controller;
late Animation<double> _iconTurns;
late Animation<double> _heightFactor;
late Animation<double> _elevation;
late Animation<Color?> _headerColor;
late Animation<Color?> _iconColor;
late Animation<Color?> _materialColor;
late Animation<EdgeInsets> _padding;
bool _isExpanded = false;
#override
void initState() {
super.initState();
_edgeInsetsTween = EdgeInsetsTween(
begin: widget.initialPadding as EdgeInsets?,
end: widget.finalPadding as EdgeInsets?,
);
_elevationTween = CurveTween(curve: widget.elevationCurve);
_heightFactorTween = CurveTween(curve: widget.heightFactorCurve);
_colorTween = CurveTween(curve: widget.colorCurve);
_turnsTween = CurveTween(curve: widget.turnsCurve);
_paddingTween = CurveTween(curve: widget.paddingCurve);
_controller = AnimationController(duration: widget.duration, vsync: this);
_heightFactor = _controller.drive(_heightFactorTween);
_iconTurns = _controller.drive(_halfTween.chain(_turnsTween));
_headerColor = _controller.drive(_headerColorTween.chain(_colorTween));
_materialColor = _controller.drive(_materialColorTween.chain(_colorTween));
_iconColor = _controller.drive(_iconColorTween.chain(_colorTween));
_elevation = _controller.drive(
Tween<double>(begin: widget.initialElevation, end: widget.elevation)
.chain(_elevationTween));
_padding = _controller.drive(_edgeInsetsTween.chain(_paddingTween));
_isExpanded = PageStorage.of(context)?.readState(context) as bool? ??
widget.initiallyExpanded;
if (_isExpanded) _controller.value = 1.0;
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
// Credit: Simon Lightfoot - https://stackoverflow.com/a/48935106/955974
void _setExpansion(bool shouldBeExpanded) {
if (shouldBeExpanded != _isExpanded) {
setState(() {
_isExpanded = shouldBeExpanded;
if (_isExpanded) {
_controller.forward();
} else {
_controller.reverse().then<void>((void value) {
if (!mounted) return;
setState(() {
// Rebuild without widget.children.
});
});
}
PageStorage.of(context)?.writeState(context, _isExpanded);
});
if (widget.onExpansionChanged != null)
widget.onExpansionChanged!(_isExpanded);
}
}
void expand() {
_setExpansion(true);
}
void collapse() {
_setExpansion(false);
}
void toggleExpansion() {
_setExpansion(!_isExpanded);
}
Widget _buildChildren(BuildContext context, Widget? child) {
return Padding(
padding: _padding.value,
child: Material(
color: Colors.grey[200],
borderRadius: widget.borderRadius,
elevation: 0,
shadowColor: widget.shadowColor,
child: Container(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
InkWell(
customBorder:
RoundedRectangleBorder(borderRadius: widget.borderRadius),
onTap: toggleExpansion,
child: ListTileTheme.merge(
iconColor: _iconColor.value,
textColor: _headerColor.value,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: ListTile(
isThreeLine: widget.isThreeLine,
contentPadding: widget.contentPadding,
leading: widget.leading,
title: widget.title,
subtitle: widget.subtitle,
trailing: RotationTransition(
turns: widget.trailing == null || widget.animateTrailing
? _iconTurns
: AlwaysStoppedAnimation(0),
child: widget.trailing ?? Icon(Icons.expand_more),
),
),
),
),
),
ClipRect(
child: Align(
heightFactor: _heightFactor.value,
child: child,
),
),
],
),
),
),
);
}
#override
void didChangeDependencies() {
final ThemeData theme = Theme.of(context);
_headerColorTween
..begin = theme.textTheme.subtitle1!.color
..end = widget.expandedTextColor ?? theme.accentColor;
_iconColorTween
..begin = theme.unselectedWidgetColor
..end = widget.expandedTextColor ?? theme.accentColor;
_materialColorTween
..begin = widget.baseColor ?? theme.canvasColor
..end = widget.expandedColor ?? theme.cardColor;
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
final bool closed = !_isExpanded && _controller.isDismissed;
return AnimatedBuilder(
animation: _controller.view,
builder: _buildChildren,
child: closed ? null : Column(children: widget.children),
);
}
}
Initialize the key value in your dart file
final GlobalKey<FormExpansionTileCardState> cardA = new GlobalKey();
final GlobalKey<FormExpansionTileCardState> cardB = new GlobalKey();
//Sender Details
FormExpansionTileCard(
key: cardA,
trailing: (senderExpansion == false)
? Icon(
MdiIcons.toggleSwitchOffOutline,
size: 40,
color: Colors.black54,
)
: Icon(MdiIcons.toggleSwitchOutline, size: 40, color: Colors.red),
onExpansionChanged: (value) {
senderExpansion = value;
},
initiallyExpanded: senderExpansion,
title: Text(
'Sender Details',
style: TextStyle(
fontWeight: FontWeight.bold,
color: senderHeadingColor,
fontSize: 20,
letterSpacing: 1),
),
children: [
//...Your Widgets
],
),
//Addressee Details
FormExpansionTileCard(
key: cardB,
trailing: (senderExpansion == false)
? Icon(
MdiIcons.toggleSwitchOffOutline,
size: 40,
color: Colors.black54,
)
: Icon(MdiIcons.toggleSwitchOutline, size: 40, color: Colors.red),
onExpansionChanged: (value) {
addresseeExpansion = value;
},
initiallyExpanded: addresseeExpansion,
title: Text(
'AddresseeDetails',
style: TextStyle(
fontWeight: FontWeight.bold,
color: senderHeadingColor,
fontSize: 20,
letterSpacing: 1),
),
children: [
//...Your Widgets
],
),
I created a simple solution to this very problem. Hope this helps.
Solution: https://github.com/TreyThomas93/flutter-expansion-tile-demo
I have a ListBuilder inside MultiPageForm which creates a Card with 2 Textfields on each "add" tap. It used to work just fine but now it isn't - On each press, on the TextField the page is rebuilt, and the keyboard closes as soon as it opens.
List<AddIngredientItem> basic_ingredients = [];
This is the ListBuilder
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: basic_ingredients.length,
itemBuilder: (BuildContext context, index) {
return Dismissible(
background: slideBackground("right"),
secondaryBackground: slideBackground("left"),
key: UniqueKey(),
onDismissed: (direction) {
setState(
() {
basic_ingredients.removeAt(index);
},
);
},
child: basic_ingredients[index]);
},
),
This is the class which creates the Card with 2 TextFields:
import 'package:flutter/material.dart';
class AddIngredientItem extends StatefulWidget {
final _ingi_name_controller = TextEditingController();
final _ingi_amount_controller = TextEditingController();
final _measurement_controller = TextEditingController();
AddIngredientItem();
#override
String toStringShort() {
return _ingi_name_controller.text;
}
#override
_AddIngredientItemState createState() => _AddIngredientItemState();
}
class _AddIngredientItemState extends State<AddIngredientItem> {
List _measurements = ["1", "2", "3", "4", "5"];
List<DropdownMenuItem<String>> _dropDownMenuItems;
List<DropdownMenuItem<String>> getDropDownMenuItems() {
List<DropdownMenuItem<String>> items = new List();
for (String city in _measurements) {
items.add(new DropdownMenuItem(value: city, child: new Text(city)));
}
return items;
}
#override
void initState() {
_dropDownMenuItems = getDropDownMenuItems();
super.initState();
}
#override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: TextFormField(
controller: widget._ingi_name_controller,
maxLines: 1,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18.0,
),
onChanged: (val) {
setState(() {});
},
decoration: InputDecoration(
border: UnderlineInputBorder(
borderSide:
BorderSide(color: Theme.of(context).primaryColor),
),
hintText: 'hint',
),
),
),
Flexible(
child: TextFormField(
controller: widget._ingi_amount_controller,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18.0,
),
maxLines: 1,
onChanged: (val) {
setState(() {});
},
decoration: InputDecoration(
border: UnderlineInputBorder(
borderSide:
BorderSide(color: Theme.of(context).primaryColor),
),
hintText: 'amount',
),
),
),
Flexible(
child: DropdownButton(
hint: Center(
child: Text("unit", style: TextStyle(fontSize: 18))),
value: widget._measurement_controller.text == ""
? null
: widget._measurement_controller.text,
items: _dropDownMenuItems,
onChanged: (val) {
widget._measurement_controller.text = val;
setState(() {});
},
)),
],
),
),
);
}
}
The issue is only about the TextField, any suggestions on how to make it work?
The problem was in the Unique.
Changing it to ValueKey helped.