Image uploads in Firebase but not the data fetched from TextFormFields and dropdownbutton - flutter

Flutter beginner here. Working on a flutter project where I can submit a form where I can upload it's content in firebase. I took the data from TextFormField and DropDownButton and the images from ImagePicker. I can upload the image file perfectly to the firebase but the data are not uploading. Here is the code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart' as path;
class AddDoctor extends StatefulWidget {
#override
State<AddDoctor> createState() => AddDoctorState();
}
class AddDoctorState extends State<AddDoctor> {
late String name;
late int age;
late String description;
String specialistValue = 'Select Specialist';
String hospitalValue = 'Select Hospital';
List<String> imageUrlList = [];
final controllerName = TextEditingController();
final controllerAge = TextEditingController();
final controllerDesciption = TextEditingController();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final FirebaseStorage _firebaseStorage = FirebaseStorage.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final ImagePicker _picker = ImagePicker();
XFile? image;
void pickDoctorImage() async {
try {
final pickedImage = await _picker.pickImage(source: ImageSource.gallery);
setState(() {
image = pickedImage!;
});
} catch (e) {}
}
Widget displayImage() {
return Image.file(File(image!.path));
}
Future<void> uploadImage() async {
Reference ref =
_firebaseStorage.ref('products/${path.basename(image!.path)}');
await ref.putFile(File(image!.path)).whenComplete(() async {
await ref.getDownloadURL().then((value) {
imageUrlList.add(value);
});
});
}
void uploadInfo() async {
CollectionReference infoRef = _firestore.collection('DoctorList');
await infoRef.doc().set({
'name': name,
'age': age,
'description': description,
'specialist': specialistValue,
'hospital': hospitalValue,
'doctorImage': imageUrlList,
}).whenComplete(() {
Navigator.pop(context);
});
}
void uploadDoctorInfo() async {
await uploadImage().whenComplete(() => uploadInfo);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFD9E4EE),
appBar: AppBar(
title: const Text('Add Doctor'),
actions: [
IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
],
),
body: Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16),
children: <Widget>[
TextFormField(
keyboardType: TextInputType.name,
validator: (value) {
if (value!.isEmpty) {
return 'Please Name must not be empty';
} else {
return null;
}
},
controller: controllerName,
decoration: const InputDecoration(
label: Text('Name'),
),
onSaved: (value) {
name = value!;
},
),
const SizedBox(height: 10),
TextFormField(
keyboardType: TextInputType.number,
validator: (value) {
if (value!.isEmpty) {
return 'Please Age must not be empty';
} else {
return null;
}
},
controller: controllerAge,
decoration: const InputDecoration(
label: Text('Age'),
),
onSaved: (value) {
age = int.parse(value!);
},
),
const SizedBox(height: 10),
DropdownButton(
borderRadius: BorderRadius.circular(30),
value: specialistValue,
items: specialistList.map<DropdownMenuItem<String>>((e) {
return DropdownMenuItem(
value: e,
child: Text(e),
);
}).toList(),
onChanged: (String? value) {
setState(() {
specialistValue = value!;
});
},
),
DropdownButton(
borderRadius: BorderRadius.circular(30),
value: hospitalValue,
items: hospitalList.map<DropdownMenuItem<String>>((e) {
return DropdownMenuItem(
value: e,
child: Text(e),
);
}).toList(),
onChanged: (String? value) {
setState(() {
hospitalValue = value!;
});
},
),
const SizedBox(height: 10),
TextFormField(
keyboardType: TextInputType.number,
validator: (value) {
if (value!.isEmpty) {
return 'Please Description must not be empty';
} else {
return null;
}
},
maxLength: 100,
maxLines: 3,
controller: controllerDesciption,
decoration: const InputDecoration(
label: Text('Description'),
),
onChanged: (value) {
description = value;
},
),
const SizedBox(height: 10),
// CircleAvatar(
// radius: 50,
// backgroundImage: image != null ? FileImage(image) : null,
// ),
InkWell(
onTap: () {
setState(() {
image = null;
});
},
child: Container(
padding: const EdgeInsetsDirectional.only(top: 60),
height: 150,
width: 150,
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
child: Center(
child: image != null
? displayImage()
: const Text(
'You have not pick any image',
style: TextStyle(fontSize: 11),
textAlign: TextAlign.center,
),
)),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () {
pickDoctorImage();
},
child: const Text('Upload Image'),
),
const SizedBox(height: 10),
ElevatedButton(
child: const Text('Submit'),
onPressed: () {
uploadDoctorInfo();
},
),
],
),
),
);
}
}
There aren't any error in the file either. I can't figure out where the source of the problem is.

Fixed it. The problem was in
void uploadDoctorInfo() async {
await uploadImage().whenComplete(() => uploadInfo);
}
I changed it to
void uploadDoctorInfo() async {
await uploadImage().whenComplete(uploadInfo);
}
And now it's working fine

You should add for the new entry instead set data in collection. Try using the following code
void uploadInfo() async {
CollectionReference infoRef = _firestore.collection('DoctorList');
await infoRef.add({
'name': name,
'age': age,
'description': description,
'specialist': specialistValue,
'hospital': hospitalValue,
'doctorImage': imageUrlList,
}).whenComplete(() {
Navigator.pop(context);
});
}
Edited
Your are referencing uploadInfo function instead of call that. updateInfo should be called by adding (), so uploadDoctorInfo will be look like.
void uploadDoctorInfo() async {
await uploadImage().whenComplete(() => uploadInfo());
}
or
void uploadDoctorInfo() async {
await uploadImage().whenComplete(uploadInfo);
}

Related

how to hide email field when a user logged with phone authentication in flutter firebase?

i have an buying and selling app in which a user can sign up with different methods like google, phone authentication and email so my question is when a user log in with phone authentication how to hide the email field from user profile.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:mobihub_2/Screens/email_verifcation_screen.dart';
import 'package:mobihub_2/Screens/search_screens/search_filter.dart';
import '../../Models/user_model.dart';
import '../home_page.dart';
class ProfileDetailScreen extends StatefulWidget {
final String? name;
final String? email;
final String? location;
final String? gender;
final bool number;
const ProfileDetailScreen(
{Key? key, required this.name, this.email, this.location, required this.gender,required this.number})
: super(key: key);
#override
State<ProfileDetailScreen> createState() => _ProfileDetailScreenState();
}
class _ProfileDetailScreenState extends State<ProfileDetailScreen> {
final _auth=FirebaseAuth.instance;
var collectionRef = FirebaseFirestore.instance.collection('UsersDetails');
var dropDownValue;
bool loading = false;
var name = TextEditingController();
var emailC = TextEditingController();
var passwordC =TextEditingController();
var city = TextEditingController();
#override
void initState() {
if (widget.location != null && widget.location!.isNotEmpty) {
setState(() {
city = TextEditingController(text: widget.location);
});
}
if (widget.name != null && widget.name!.isNotEmpty) {
setState(() {
name = TextEditingController(text: widget.name);
});
}
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.amberAccent,
foregroundColor: Colors.blueGrey,
title: Text('Profile'),
elevation: 0,
),
body: SafeArea(
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
children: [
TextFormField(
onChanged: (value) {
setState(() {
name = TextEditingController(text: (value));
});
},
initialValue: name.text,
decoration: InputDecoration(
hintText: 'Name',
prefixIcon: Icon(Icons.person_outline_outlined),
suffixIcon: Icon(Icons.edit_outlined),
),
),
SizedBox(
height: 20,
),
TextFormField(
onTap: (){
showDialog(context: context, builder: (context) {
return AlertDialog(
content: Text('Want to change your email?'),
actions: [
Padding(padding:EdgeInsets.all(10),
child: Column(
children: [
TextFormField(
controller:emailC,
decoration: InputDecoration(
hintText: 'new email',
prefixIcon: Icon(Icons.email_outlined),
),
),
SizedBox(height: 10,),
TextFormField(
controller: passwordC,
decoration: InputDecoration(
hintText: 'old Password',
prefixIcon: Icon(Icons.lock_open_outlined),
),
),
SizedBox(height: 5,),
ElevatedButton(onPressed: ()async{
loading=true;
await changeEmail(
email: emailC.text,password: passwordC.text
);
loading=false;
}, child:loading?Center(child: CircularProgressIndicator(color:Colors.black,),): Text('Submit'))
],
),
)
],
);
}
);
},
readOnly: true,
initialValue: widget.email,
// onChanged: (value) {
// setState(() {
// email = value;
// });
// },
decoration: InputDecoration(
hintText: 'Email',
prefixIcon: Icon(Icons.email_outlined),
suffixIcon: IconButton(onPressed: () {
}, icon: Icon(Icons.edit_outlined),),
),
),
SizedBox(
height: 20,
),
TextFormField(
readOnly: true,
initialValue: widget.number.toString(),
decoration: InputDecoration(
hintText: 'Phone number',
prefixIcon: Icon(Icons.email_outlined),
suffixIcon: IconButton(onPressed: () {
}, icon: Icon(Icons.edit_outlined),),
),
),
SizedBox(
height: 20,
),
TextFormField(
onTap: (){
setState(() async {
city.text = await Navigator.push(context,
MaterialPageRoute(builder: (_) => SearchScreen()));
});
},
readOnly: true,
controller: city,
decoration: InputDecoration(
hintText: 'Address',
prefixIcon: Icon(Icons.location_city_outlined),
suffixIcon: IconButton(onPressed: () {
}, icon: Icon(Icons.arrow_drop_down_circle_outlined)),
),
),
SizedBox(
height: 20,
),
DropdownButtonFormField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.person,),
),
hint: Text(widget.gender!.isNotEmpty
? widget.gender!
: 'Select Gender'),
items: ['Male', 'Female'].map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(), onChanged: (String? val) {
setState(
() {
dropDownValue = val;
},
);
},),
SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
OutlinedButton(onPressed: () {}, child: Text('Cancel')),
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.amberAccent),
onPressed: () async {
setState(() {
loading = true;
});
await postDetailsToFirestore();
setState(() {
loading = false;
});
},
child: loading
? Center(
child: CircularProgressIndicator(color: Colors.blue,))
: Text('Save')),
],
),
],
),
),
),
);
}
Future<void> changeEmail(
{required String email, required String password}) async {
//showLoadingDialog(message: AppTexts.updating);
try {
User user = _auth.currentUser!;
DocumentReference ref=collectionRef.doc(user.uid);
final cred =
EmailAuthProvider.credential(email: user.email!, password: passwordC.text);
user.reauthenticateWithCredential(cred).then((value) {
user.updateEmail(email).then((_) async {
await ref.update({'email': email}).then((value) async {
// go to root page
await _auth.currentUser!.sendEmailVerification();
Fluttertoast.showToast(msg: 'Verification Email has been sent to you');
await _auth.signOut();
});
setState(() {
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_)=>VerificationScreen()), (route) => false);
});
}).catchError((error) {
Fluttertoast.showToast(msg: error.toString());
});
}).catchError((err) {
Fluttertoast.showToast(msg: err.toString());
});
} on Exception catch (err) {
Fluttertoast.showToast(msg: err.toString());
// showErrorDialog(err.toString());
}
}
postDetailsToFirestore() async {
var auth = FirebaseAuth.instance;
FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
User? user = auth.currentUser;
UserModel userModel = UserModel();
// writing all the values
userModel.email = user!.email;
userModel.uid = user.uid;
userModel.fullName = name.text;
userModel.phone = user.phoneNumber;
userModel.location = city.text;
userModel.joindate = '';
userModel.gender = dropDownValue;
await firebaseFirestore
.collection("UsersDetails")
.doc(user.uid)
.update(userModel.toMap());
Fluttertoast.showToast(msg: "Account Updated successfully :) ");
Navigator.pushAndRemoveUntil(
(context),
MaterialPageRoute(builder: (context) => const Home()),
(route) => false);
}
}
when a user login with email the phone field will hide and when user login with phone the email field will hide. If anybody can help me it would be very appreciable.Thank you

Put request in flutter ,does not update the object when I change only one field on edit screen

When making a put request and I edit all the field the object is updated. But if I only update one field on the edit screen than the object is not update. I think this is because the unchanged controllers do not keep the value that is already given to them in the Set state method.
Can anyone give me some hints how to solve this ?
import 'package:fin_app/apiservice/variables.dart';
import 'package:fin_app/screens/reminder/reminder_screen.dart';
import 'package:fin_app/screens/login_screen/login_screen.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../models/user.dart';
import '../monthly_expense/expense_app_theme.dart';
const String myhomepageRoute = '/';
const String myprofileRoute = 'profile';
class ReminderEdit extends StatefulWidget {
final String id;
final String title;
final String date;
final int amount;
const ReminderEdit({Key? key, required this.id, required this.title,required this.date,required this.amount}) : super(key: key);
#override
_ReminderEditState createState() => _ReminderEditState();
}
class _ReminderEditState extends State<ReminderEdit> {
final GlobalKey<FormState> _formKey = GlobalKey();
String id="";
final TextEditingController _titleInput = TextEditingController();
final TextEditingController _dateInput = TextEditingController();
final TextEditingController _amountInput = TextEditingController();
Future<Reminder>? _futureReminder;
Future<void> edit(String id,String title,String date,int amount) async {
Map<String, String> requestHeaders = {
'Content-type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $token'
};
final editReminder = jsonEncode({
'title': title,
'date': date,
'amount': amount
});
if (_titleInput.text.isNotEmpty &&
_dateInput.text.isNotEmpty ) {
var response = await http.put(
Uri.parse("$baseUrl/reminder/me/$id/details"),
body: editReminder,
headers: requestHeaders);
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Profile edited succesfully.")));
} else {
print(response.statusCode);
print(id);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Could not edit the reminder.")));
}
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Please fill out the field.")));
}
}
#override
void initState() {
_dateInput.text = ""; //set the initial value of text field
super.initState();
setState(() {
id = widget.id;
_titleInput.text = widget.title;
_dateInput.text = widget.date;
_amountInput.text=widget.amount.toString();
});
}
void navigate() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AlarmPage()),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ExpenseAppTheme.background,
body: Padding(
padding: const EdgeInsets.all(16.0),
child: ListView(
children: <Widget>[
const Align(
alignment: Alignment.topLeft,
child: Text("Edit reminder",
style: TextStyle(
fontSize: 24,
)),
),
const SizedBox(
height: 20,
),
Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
TextFormField(
controller: _titleInput,
decoration: const InputDecoration(
labelText: 'Title',
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
borderSide:
BorderSide(color: Colors.grey, width: 0.0),
),
border: OutlineInputBorder()),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null ||
value.isEmpty ||
value.contains(RegExp(r'^[a-zA-Z\-]'))) {
return 'Use only text!';
}
},
),
const SizedBox(
height: 20,
),
TextFormField(
controller:_dateInput, //editing controller of this TextField
decoration: InputDecoration(
icon: Icon(Icons.calendar_today), //icon of text field
labelText: "Enter Date" //label text of field
),
readOnly:
true, //set it true, so that user will not able to edit text
onTap: () async {
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(
2000), //DateTime.now() - not to allow to choose before today.
lastDate: DateTime(2101));
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); //formatted date output using intl package => 2021-03-16
//you can implement different kind of Date Format here according to your requirement
setState(() {
_dateInput.text =
formattedDate; //set output date to TextField value.
});
} else {
print("Date is not selected");
}
},
),
const SizedBox(
height: 35,
),
TextFormField(
controller: _amountInput,
decoration: const InputDecoration(
labelText: 'Amount',
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
borderSide:
BorderSide(color: Colors.grey, width: 0.0),
),
border: OutlineInputBorder()),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Amount must not be empty';
} else if (value.contains(RegExp(r'^[0-9]*$'))) {
return 'Amount must only contain numbers';
}
},
), const SizedBox(
height: 20,
),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(60)),
onPressed: () {
edit(widget.id,_titleInput.text,_dateInput.text,int.parse(_amountInput.text));
navigate();
},
child: const Text("Submit"),
),
],
),
),
],
),
),
);
}
FutureBuilder<Reminder> buildFutureBuilder() {
return FutureBuilder<Reminder>(
future: _futureReminder,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('Expense added');
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
);
}
}

How to validate textfield when posting to firestore in flutter? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 11 months ago.
Improve this question
I have added validator in TextField but validator is not working while submitting data. Save button saves the data with null value.
import 'package:cloud_firestore/cloud_firestore.dart';
import '../../screens/orders/order_success.dart';
import '../../services/global_methods.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class PlaceOrder extends StatefulWidget {
static const routeName = '/place-order';
const PlaceOrder({Key? key}) : super(key: key);
#override
State<PlaceOrder> createState() => _PlaceOrderState();
}
class _PlaceOrderState extends State<PlaceOrder> {
final _formKey = GlobalKey<FormState>();
String? _name;
int? _phoneNumber;
String? _fullAddress;
String? _area;
String? _city;
String? _addressType;
final TextEditingController _addressController = TextEditingController();
String? _addressValue;
String? _deliverySlotValue;
final FirebaseAuth _auth = FirebaseAuth.instance;
late bool _isLoading = false;
final GlobalMethods _globalMethods = GlobalMethods();
final FocusNode _numberFocusNode = FocusNode();
#override
void initState() {
setState(() {
_isLoading = false;
});
super.initState();
}
#override
void dispose() {
_numberFocusNode.dispose();
super.dispose();
}
void _submitData() async {
final _isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (_isValid) {
_formKey.currentState!.save();
}
try {
setState(() {
_isLoading = true;
});
final User? user = _auth.currentUser;
final _uid = user!.uid;
final orderId = ModalRoute.of(context)!.settings.arguments as String;
FirebaseFirestore.instance
.collection('orders')
.doc(orderId)
.set({
'userId': _uid,
'name': _name,
'phoneNumber': _phoneNumber,
'addressType': _addressType,
'address': _fullAddress,
'area': _area,
'city': _city,
'deliverySlot': _deliverySlotValue,
}, SetOptions(merge: true));
} catch (error) {
_globalMethods.authDialog(context, error.toString());
} finally {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushReplacementNamed(OrderSuccess.routeName);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add new address'),
),
body: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
DropdownButton<String>(
items: const [
DropdownMenuItem<String>(
child: Text('Home'),
value: 'Home',
),
DropdownMenuItem<String>(
child: Text('Office'),
value: 'Office',
),
DropdownMenuItem<String>(
child: Text('Other'),
value: 'Other',
),
],
onChanged: (value) {
setState(() {
_addressValue = value.toString();
_addressController.text = value.toString();
print(_addressType);
});
},
hint: const Text('Select address type'),
value: _addressValue,
),
const SizedBox(
width: 10,
),
Expanded(
child: TextFormField(
key: const ValueKey('addressType'),
controller: _addressController,
validator: (val) {
if (val!.isEmpty) {
return 'Please select address type';
}
return null;
},
decoration: const InputDecoration(
labelText: 'Select your address type',
),
onSaved: (val) {
_addressType = val.toString();
},
),
),
],
),
TextFormField(
onSaved: (value) {
_name = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('name'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your name';
}
return null;
},
decoration: InputDecoration(
labelText: 'Name',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_fullAddress = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('streetAddress'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your address';
}
return null;
},
decoration: InputDecoration(
labelText: 'Address',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_area = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('area'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your area';
}
return null;
},
decoration: InputDecoration(
labelText: 'Area',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_city = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('city'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your city';
}
return null;
},
decoration: InputDecoration(
labelText: 'City',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_phoneNumber = int.parse(value!);
},
textInputAction: TextInputAction.next,
// keyboardType: TextInputType.emailAddress,
onEditingComplete: () =>
FocusScope.of(context).requestFocus(_numberFocusNode),
key: const ValueKey('number'),
validator: (value) {
if (value!.length < 10) {
return 'Phone number must be 10 units';
}
return null;
},
decoration: InputDecoration(
labelText: 'Phone Number',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.phone),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DropdownButton<String>(
items: const [
DropdownMenuItem<String>(
child: Text('11:00am to 1:00pm'),
value: '11:00am to 1:00pm',
),
DropdownMenuItem<String>(
child: Text('1:00pm to 3:00pm'),
value: '1:00pm to 3:00pm',
),
DropdownMenuItem<String>(
child: Text('3:00pm to 5:00pm'),
value: '3:00pm to 5:pm',
),
DropdownMenuItem<String>(
child: Text('5:00pm to 7:00pm'),
value: '5:00pm to 7:00pm',
),
DropdownMenuItem<String>(
child: Text('7:00pm to 9:pm'),
value: '7:00pm to 9:pm',
),
],
onChanged: (value) {
setState(() {
_deliverySlotValue = value.toString();
});
},
hint: const Text('Select Delivery Slot'),
value: _deliverySlotValue,
),
],
),
Padding(
padding: const EdgeInsets.all(48.0),
child: SizedBox(
child: ElevatedButton(
onPressed: _submitData,
child: _isLoading
? const CircularProgressIndicator()
: const Text(
'S U B M I T',
style: TextStyle(color: Colors.white),
),
),
),
),
],
),
),
),
),
);
}
}
It appears that the problem is a simple mistake with your control flow in the following code (see // comment):
void _submitData() async {
final _isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (_isValid) {
_formKey.currentState!.save();
} // <----- this should not be here!
try {
setState(() {
_isLoading = true;
});
final User? user = _auth.currentUser;
final _uid = user!.uid;
final orderId = ModalRoute.of(context)!.settings.arguments as String;
FirebaseFirestore.instance
.collection('orders')
.doc(orderId)
.set({
'userId': _uid,
'name': _name,
'phoneNumber': _phoneNumber,
'addressType': _addressType,
'address': _fullAddress,
'area': _area,
'city': _city,
'deliverySlot': _deliverySlotValue,
}, SetOptions(merge: true));
} catch (error) {
_globalMethods.authDialog(context, error.toString());
} finally {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushReplacementNamed(OrderSuccess.routeName);
}
}
As you can see, even if the user input is not valid, you still continue to upload the results to Firebase anyway.
Try correcting like this:
void _submitData() async {
final _isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (_isValid) {
_formKey.currentState!.save();
try {
setState(() {
_isLoading = true;
});
final User? user = _auth.currentUser;
final _uid = user!.uid;
final orderId = ModalRoute.of(context)!.settings.arguments as String;
FirebaseFirestore.instance.collection('orders').doc(orderId).set({
'userId': _uid,
'name': _name,
'phoneNumber': _phoneNumber,
'addressType': _addressType,
'address': _fullAddress,
'area': _area,
'city': _city,
'deliverySlot': _deliverySlotValue,
}, SetOptions(merge: true));
} catch (error) {
_globalMethods.authDialog(context, error.toString());
} finally {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushReplacementNamed(OrderSuccess.routeName);
}
} else {
// do nothing
}
}
The only possible reason is because of _addressType it's initial value is null
since it's a String? variable so if the user didn't select a "Delivery Slot"
the result of it after saving would be null,
what you need to do, it to check the value of _addressType if it's null or not then proceed to saving the form because your form doesn't check it if it's null or not.
You can add the form validator to the submit button itself. The below code works when you click on the submit button.
Padding(
padding: const EdgeInsets.all(48.0),
child: SizedBox(
child: ElevatedButton(
onPressed: (){
if (_formKey.currentState!.validate()){
_submitData;
}
},
child: _isLoading
? const CircularProgressIndicator()
: const Text(
'S U B M I T',
style: TextStyle(color: Colors.white),
),
),
),
),

Flutter: How to mimic calculator display with TextFormField?

I'm implementing code below to save numeric data to database.
The saved numeric data will be used in arithmetic operations in the future.
The code is still quite native where numeric number is not separated by thousands.
The inserted value is equal to the retrieved value with this practice.
I want to give the user an experience mimicking physical calculator.
To achieve that I tried to use Masking Controller or Input Formatter Packages available in the pub.dev.
So far, I haven't get the result I was looking for.
In the commented out code below, is one of the example practice where I tried to separate those numbers by thousands and decimals when inserting and retrieving numeric data by using currency_text_input_formatter package.
In this practice, not all digits were saved to the database,
any number inserted above three digits always resulted in only three digits and two decimals saved and retrieved.
Examples:
1. value inserted: 7000
print('value: $value')
the result was:
I/flutter (12551): value: 700.0
2. value inserted: 12,345,678.90
print('value: $value')
the result was:
I/flutter (12551): value: 123.45
Is there any suggestion which package or method I should implement?
And how to make the implementation of the package or method to get the desired result?
My original native code as shown below,
please uncomment the commented out code to reproduce my problem.
Any help would be highly appreciated.
my Model:
class Product {
int id;
String name;
num savedValue;
static const tblProduct = 'product';
static const colId = 'id';
static const colName = 'name';
static const colSavedValue = 'savedValue';
Product({
this.id,
this.name,
this.savedValue,
});
Map<String, dynamic> toMap() {
var map = <String, dynamic>{
colName: name,
colSavedValue: savedValue,
};
if (id != null) map[colId] = id;
return map;
}
Product.fromMap(Map<String, dynamic> map) {
id = map[colId];
name = map[colName];
savedValue = map[colSavedValue];
}
}
my UI:
import 'dart:ui';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:sqflite/sqflite.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:currency_text_input_formatter/currency_text_input_formatter.dart';
class LossZero extends StatefulWidget {
#override
_LossZeroState createState() => _LossZeroState();
}
class _LossZeroState extends State<LossZero> {
DatabaseHelper dbHelper = DatabaseHelper.instance;
Product product = Product();
List<Product> products = [];
String name;
int id;
num value;
final _fbKey = GlobalKey<FormBuilderState>();
final itemController = TextEditingController();
final savedValueController = TextEditingController();
final retrievedValueController = TextEditingController();
final itemChosen = TextEditingController();
#override
void initState() {
super.initState();
dbHelper = DatabaseHelper.instance;
refreshItems();
}
#override
Widget build(BuildContext context) {
return Container(
child: SafeArea(
child: Scaffold(
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
FormBuilder(
key: _fbKey,
child: Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
height: 70,
margin: EdgeInsets.symmetric(horizontal: 15),
child: FormBuilderTextField(
attribute: 'item',
controller: itemController,
autofocus: true,
textAlign: TextAlign.start,
textAlignVertical: TextAlignVertical.bottom,
textInputAction: TextInputAction.next,
keyboardType: TextInputType.name,
inputFormatters: [],
decoration: InputDecoration(
helperText: ' ',
hintText: 'Item',
hintStyle: TextStyle(fontSize: 12),
prefixIcon: Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Icon(
FontAwesomeIcons.shoppingBag,
size: 20,
),
)),
onChanged: (val) {
setState(() {
name = val;
_fbKey.currentState.fields['item'].currentState
.validate();
});
},
autovalidateMode: AutovalidateMode.onUserInteraction,
validators: [
FormBuilderValidators.required(
errorText: 'required',
),
],
),
),
Container(
width: MediaQuery.of(context).size.width,
height: 70,
margin: EdgeInsets.symmetric(horizontal: 15),
child: FormBuilderTextField(
attribute: 'value',
controller: savedValueController,
textAlign: TextAlign.start,
textAlignVertical: TextAlignVertical.bottom,
textInputAction: TextInputAction.next,
keyboardType: TextInputType.number,
inputFormatters: [
// CurrencyTextInputFormatter(
// decimalDigits: 2,
// ),
],
decoration: InputDecoration(
helperText: ' ',
hintText: 'Saved Value',
hintStyle: TextStyle(fontSize: 12),
prefixIcon: Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Icon(
FontAwesomeIcons.save,
size: 20,
),
)),
onChanged: (val) {
setState(() {
value = num.parse(val);
_fbKey.currentState.fields['value'].currentState
.validate();
});
},
autovalidateMode: AutovalidateMode.onUserInteraction,
validators: [
FormBuilderValidators.required(
errorText: 'required',
),
],
),
),
SizedBox(height: 50),
RaisedButton(
child: Text('Save'),
onPressed: saveForm,
),
SizedBox(height: 40),
Container(
color: Colors.grey[200],
width: MediaQuery.of(context).size.width,
height: 70,
margin: EdgeInsets.symmetric(horizontal: 20),
child: FormBuilderTypeAhead(
attribute: 'item_chosen',
initialValue: product,
getImmediateSuggestions: true,
autoFlipDirection: true,
controller: itemChosen,
decoration: InputDecoration(
border: InputBorder.none,
),
hideOnLoading: true,
onChanged: (val) {},
itemBuilder: (context, Product product) {
return ListTile(
title: Text(product.name),
subtitle: Text(product.savedValue.toString()),
);
},
selectionToTextTransformer: (Product ps) => ps.name,
suggestionsCallback: (query) {
if (query.isNotEmpty) {
var lowercaseQuery = query.toLowerCase();
return products.where((product) {
return product.name
.toLowerCase()
.contains(lowercaseQuery);
}).toList(growable: false)
..sort((a, b) => a.name
.toLowerCase()
.indexOf(lowercaseQuery)
.compareTo(b.name
.toLowerCase()
.indexOf(lowercaseQuery)));
} else {
return products;
}
},
textFieldConfiguration: TextFieldConfiguration(
autofocus: true,
style: DefaultTextStyle.of(context).style.copyWith(
fontSize: 24,
letterSpacing: 1.2,
color: Colors.black,
fontWeight: FontWeight.w300,
decoration: TextDecoration.none),
),
noItemsFoundBuilder: (BuildContext context) {
return Text('not registered');
},
onSuggestionSelected: (product) {
if (product != null) {
setState(() {
retrievedValueController.text =
product.savedValue.toString();
});
} else {
return products;
}
},
),
),
Container(
width: MediaQuery.of(context).size.width,
height: 70,
margin: EdgeInsets.symmetric(horizontal: 15),
child: FormBuilderTextField(
attribute: 'retrieve_value',
controller: retrievedValueController,
textAlign: TextAlign.start,
textAlignVertical: TextAlignVertical.bottom,
textInputAction: TextInputAction.next,
keyboardType: TextInputType.number,
inputFormatters: [
// CurrencyTextInputFormatter(
// decimalDigits: 2,
// )
],
decoration: InputDecoration(
helperText: ' ',
hintText: 'Retrieved Value',
hintStyle: TextStyle(fontSize: 12),
prefixIcon: Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Icon(
FontAwesomeIcons.list,
size: 20,
),
)),
onChanged: (val) {},
validators: [],
),
),
],
),
),
],
),
),
),
),
);
}
saveForm() async {
if (_fbKey.currentState.validate()) {
_fbKey.currentState.save();
if (product.id == null) {
Product p = Product(
id: null,
name: name,
savedValue: value,
);
await insertValue(p);
itemController.clear();
savedValueController.clear();
refreshItems();
Get.snackbar('Done', 'Item Saved');
print('value: $value');
} else {
Get.snackbar('Fail', 'Item saving Failed');
}
}
}
refreshItems() async {
List<Product> p = await getAllItems();
setState(() {
products = p;
});
}
Future<int> insertValue(Product prod) async {
Database db = await dbHelper.database;
return await db.insert(Product.tblProduct, prod.toMap());
}
Future<List<Product>> getAllItems() async {
Database db = await dbHelper.database;
List<Map> x = await db.query(Product.tblProduct);
return x.length == 0
? []
: x.map((e) => Product.fromMap(e)).toList();
}
}
my dbHelper
import 'dart:io';
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
class DatabaseHelper {
static const _databaseVersion = 1;
static const _databaseName = 'product.db';
DatabaseHelper._();
static final DatabaseHelper instance = DatabaseHelper._();
Database _database;
Future<Database> get database async {
if (_database != null) return _database;
_database = await _initDatabase();
return _database;
}
_initDatabase() async {
Directory dataDirectory = await getApplicationDocumentsDirectory();
String dbPath = join(dataDirectory.path, _databaseName);
return await openDatabase(
dbPath,
version: _databaseVersion,
onCreate: _onCreateDB,
);
}
_onCreateDB(Database db, int version) async {
await db.execute('''
-- P R O D U C T
CREATE TABLE ${Product.tblProduct}(
${Product.colId} INTEGER INCREMENT,
${Product.colName} TEXT NOT NULL,
${Product.colSavedValue} FLOA
)
''');
}
}
I finally get the result I was looking for after using this package as many has recommended.
On the code above, I modified following methods:
saving method:
onChanged: (val) {
setState(() {
value =
savedValueController.numberValue.toDouble();
_fbKey.currentState.fields['value'].currentState
.validate();
});
},
retrieve data method:
onSuggestionSelected: (product) {
if (product != null) {
setState(() {
var x = product.savedValue;
retrievedValueController
.updateValue(x.toDouble());
});
} else {
return products;
}
},
modifying controller method
modified TexEditingController:
final savedValueController =
MoneyMaskedTextController(decimalSeparator: '.', thousandSeparator: ',');
final retrievedValueController =
MoneyMaskedTextController(decimalSeparator: '.', thousandSeparator: ',');
putting inputFormatter in FormBuilder:
inputFormatters: [],

Flutter: How to assign other value (object) as Text Editing Controller in Another Field?

I'm building a form that has contact name and phone number fields. User will be able to choose (tap) contact from previously saved contact list, and this should display name and phone numbers at their respective fields.
To achieve that I'm using TypeAheadFormField from Flutter_Form_Builder Package version: 3.14.0 to build my form.
I successfully assign _nameController from local database in the default TypeAheadFormField controller.
But I can't assign _mobileController from the same choice I tapped to FormBuilderTextField.
I managed to get "name'-value with TypeAheadFormField, but everytime I switch the choices from suggestions,
the _mobileController.text didn't update on FormBuilderTextField
My code as follow:
import 'package:myApp/customer.dart';
import 'package:myApp/db_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
class MyForm extends StatefulWidget {
#override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
DatabaseHelper _dbHelper;
Customer _customer = Customer();
List<Customer> _customerList = [];
final _formKey = GlobalKey<FormBuilderState>();
final _cfKey = GlobalKey<FormBuilderState>();
final _nameController = TextEditingController();
final _inputContactNameController = TextEditingController();
final _inputContactPhoneController = TextEditingController();
var _mobileController = TextEditingController();
#override
void initState() {
super.initState();
_refreshBikeSellerList();
setState(() {
_dbHelper = DatabaseHelper.instance;
});
_mobileController = TextEditingController();
_mobileController.addListener(() {
setState(() {});
});
}
#override
void dispose() {
_mobileController.dispose();
_nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
child: FormBuilder(
key: _formKey,
child: Column(
children: [
FormBuilderTypeAhead(
attribute: 'contact_person',
initialValue: _customer,
controller: _nameController,
onChanged: (val) {},
itemBuilder: (context, Customer _customer) {
return ListTile(
title: Text(_customer.name),
subtitle: Text(_customer.mobile),
);
},
selectionToTextTransformer: (Customer c) => c.name,
suggestionsCallback: (query) {
if (query.isNotEmpty) {
var lowercaseQuery = query.toLowerCase();
return _customerList.where((_customer) {
return _customer.name
.toLowerCase()
.contains(lowercaseQuery);
}).toList(growable: false)
..sort((a, b) => a.name
.toLowerCase()
.indexOf(lowercaseQuery)
.compareTo(
b.name.toLowerCase().indexOf(lowercaseQuery)));
} else {
return _customerList;
}
},
textFieldConfiguration: TextFieldConfiguration(
autofocus: true,
style: DefaultTextStyle.of(context).style.copyWith(
fontSize: 17,
letterSpacing: 1.2,
color: Colors.black,
fontWeight: FontWeight.w300,
),
// controller: guessMotor1,
),
onSuggestionSelected: (val) {
if (val != null) {
return _customerList.map((_customer) {
setState(() {
_mobileController.text = _customer.mobile;
});
}).toList();
} else {
return _customerList;
}
},
),
FormBuilderTextField(
controller: _mobileController,
attribute: 'mobile',
readOnly: true,
style: TextStyle(fontSize: 17),
decoration: InputDecoration(
hintText: 'mobile',
),
),
SizedBox(height: 40),
Container(
child: RaisedButton(
onPressed: () async {
await manageContact(context);
},
child: Text('Manage Contact'),
),
),
],
),
),
);
}
manageContact(BuildContext context) async {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(
'Manage Contact',
textAlign: TextAlign.center,
),
titleTextStyle: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 17,
color: Colors.black45,
letterSpacing: 0.8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12))),
content: FormBuilder(
key: _cfKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// SizedBox(height: 10),
InkResponse(
onTap: () {},
child: CircleAvatar(
radius: 30,
child: Icon(
Icons.person_add,
color: Colors.grey[100],
),
backgroundColor: Colors.grey[500],
),
),
SizedBox(height: 10),
Container(
width: MediaQuery.of(context).size.width * 0.5,
margin: EdgeInsets.symmetric(horizontal: 15),
child: FormBuilderTextField(
maxLength: 20,
controller: _inputContactNameController,
textAlign: TextAlign.start,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.words,
attribute: 'contact_person',
decoration: InputDecoration(
prefixIcon: Icon(
Icons.person_outline,
size: 22,
)),
onChanged: (val) {
setState(() {
_customer.name = val;
_formKey
.currentState.fields['contact_person'].currentState
.validate();
});
},
autovalidateMode: AutovalidateMode.always,
validators: [
FormBuilderValidators.required(),
FormBuilderValidators.maxLength(20),
FormBuilderValidators.minLength(2),
],
),
),
Container(
width: MediaQuery.of(context).size.width * 0.5,
margin: EdgeInsets.symmetric(horizontal: 15),
child: FormBuilderTextField(
attribute: 'phone_number',
controller: _inputContactPhoneController,
textAlign: TextAlign.start,
keyboardType: TextInputType.number,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.phone_android,
size: 22,
)),
onChanged: (val) {
setState(() {
_customer.mobile = val;
_formKey.currentState.fields['phone_number'].currentState
.validate();
});
},
validators: [
FormBuilderValidators.required(),
FormBuilderValidators.numeric(),
],
valueTransformer: (text) {
return text == null ? null : num.tryParse(text);
},
),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
RaisedButton(
color: Colors.white,
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
}),
RaisedButton(
color: Colors.grey[400],
child: Text(
'Save',
style: TextStyle(color: Colors.white),
),
onPressed: () async {
try {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
if (_customer.id == null)
await _dbHelper.insertBikeContact(_customer);
else
await _dbHelper.updateCustomer(_customer);
_refreshBikeSellerList();
_formKey.currentState.reset();
_inputContactNameController.clear();
_inputContactPhoneController.clear();
Navigator.of(context).pop();
}
} catch (e) {
print(e);
}
},
)
],
),
],
),
),
),
);
}
_refreshBikeSellerList() async {
List<Customer> x = await _dbHelper.getCustomer();
setState(() {
_customerList = x;
});
}
}
Is there any possible way to update _mobileController as I tap?
Any help would be much appreciated.
Thank you in advance.
EDITED:
class where I save the customer data:
class Customer {
int id;
String name;
String mobile;
static const tblCustomer = 'Customer';
static const colId = 'id';
static const colName = 'name';
static const colMobile = 'mobile';
Customer({
this.id,
this.name,
this.mobile,
});
Map<String, dynamic> toMap() {
var map = <String, dynamic>{colName: name, colMobile: mobile};
if (id != null) map[colId] = id;
return map;
}
Customer.fromMap(Map<String, dynamic> map) {
id = map[colId];
name = map[colName];
mobile = map[colMobile];
}
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is Customer &&
runtimeType == other.runtimeType &&
name == other.name;
#override
int get hashCode => name.hashCode;
#override
String toString() {
return name;
}
}
Here is my database:
import 'dart:async';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'customer.dart';
class DatabaseHelper {
static const _databaseVersion = 1;
static const _databaseName = 'Kiloin.db';
DatabaseHelper._();
static final DatabaseHelper instance = DatabaseHelper._();
Database _database;
Future<Database> get database async {
if (_database != null) return _database;
_database = await _initDatabase();
return _database;
}
_initDatabase() async {
Directory dataDirectory = await getApplicationDocumentsDirectory();
String dbPath = join(dataDirectory.path, _databaseName);
return await openDatabase(
dbPath,
version: _databaseVersion,
onCreate: _onCreateDB,
);
}
_onCreateDB(Database db, int version) async {
await db.execute('''
CREATE TABLE ${Customer.tblCustomer}(
${Customer.colId} INTEGER PRIMARY KEY AUTOINCREMENT,
${Customer.colName} TEXT NOT NULL,
${Customer.colMobile} TEXT NOT NULL
)
''');
}
Future<int> insertBikeContact(Customer customer) async {
Database db = await database;
return await db.insert(Customer.tblCustomer, customer.toMap());
}
Future<List<Customer>> getCustomer() async {
Database db = await database;
List<Map> contact = await db.query(Customer.tblCustomer);
return contact.length == 0
? []
: contact.map((e) => Customer.fromMap(e)).toList();
}
Future<int> updateCustomer(Customer customer) async {
Database db = await database;
return await db.update(Customer.tblCustomer, customer.toMap(),
where: '${Customer.colId}=?', whereArgs: [customer.id]);
}
Future<int> deleteContact(int id) async {
Database db = await database;
return await db.delete(Customer.tblCustomer,
where: '${Customer.colId}=?', whereArgs: [id]);
}
}
The value that you get from onSuggestionSelected is the customer. Use that value to update _mobileController.text.
onSuggestionSelected: (customer) {
if (customer != null) {
setState(() {
_mobileController.text = customer.mobile;
});
}
}