Flutter Uploading Text File to Storage - firebase

I'm trying to create a .txt file that contains the information from TextFieldController.
class FileUtils {
static Future<String> get getFilePath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
static Future<File> get getFile async {
final path = await getFilePath;
return File('$path/myfile.txt');
}
static Future<File> saveToFile(String data) async {
final file = await getFile;
final neww = file.writeAsString(data);
return file.writeAsString(data);
}
static Future<String> readFromFile() async {
try {
final file = await getFile;
String fileContents = await file.readAsString();
return fileContents;
} catch (e) {
return "";
}
}
}
So, in the code above, I'm trying to create a .txt file that will be stored in Firebase Storage. In the code below, the user will input the information and that information will be saved to text file using the FileUtils' saveToFile function.
_dialog(BuildContext context) async {
await Future.delayed(Duration(seconds: 1));
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Please write something'),
content: TextField(
controller: _textFieldController,
decoration: InputDecoration(hintText: ""),
maxLines: 5,
maxLength: 250,
),
actions: <Widget>[
new FlatButton(
child: new Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
new FlatButton(
child: new Text('Submit'),
onPressed: () {
FileUtils.saveToFile(_textFieldController.text);
Navigator.of(context).pop();
},
)
],
);
});
}
And I tried to store the text file using:
final test = FileUtils.getFile;
FirebaseStorage.instance.ref().child('test').putFile(test).onComplete;
but I failed. So, I need help storing .txt file to Firebase. I'm confused so I hope I was able to explain my problem.

Related

Unable to upload data to firebase realtime database from my flutter application

I am trying to add a module in my flutter project where users will be able to upload their addresses from. I'm using a http package for this. On saving the data, however, only an empty string is being uploaded onto the database.
Here's my code:
This is the validator code from the text form editing field:
validator: (value) {
if (value!.isEmpty) {
return 'Please enter a description.';
}
if (value.length < 10) {
return 'Should be at least 10 characters long.';
}
return null;
},
onSaved: (value) {
addressEditingController.text = value!;
_editedAddress = Address(address: value);
},
),
),
IconButton(onPressed:(){
if(_form.currentState!.validate()){
uploadAddress();
Here is the uploadAddress() function:
void uploadAddress() {
setState(() {
_editedAddress = Address(
address: _editedAddress.address);
});
Provider.of<addAddress>(context, listen: false)
.addProduct(_editedAddress)
.catchError((error) {
return showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text("An error occurred"),
content: Text(error.toString()),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(ctx).pop();
},
child: Text("Okay"))
],
));
}).then((_) {
setState(() {
_isLoading = false;
});
});
}
Here's the addProduct() function:
Future<void> addProduct(Address address) {
final url = Uri.https(
'internship-75b58-default-rtdb.firebaseio.com', '/addresses/${user!.phoneNumber}.json');
return http
.post(url,
body: json.encode({
'address':address.address,
}))
.then((response) {
final newProduct = Address(
address: address.address,);
notifyListeners();
}).catchError((error) {
print(error);
throw error;
});
}
Any help would be greatly appreciated:)

My question is about how to preview an image before it is uploaded to firebase in flutter? (image_picker_package)(provider)

In firebase Storage Service Dart File:
//storage reference
Future<String> uploadUserImage(File? image, String path) async {
String imageUrl;
Reference ref =
_firebaseStorage.ref().child('userImages').child('$path.jpg');
await ref.putFile(image!);
imageUrl = await ref.getDownloadURL();
return imageUrl;
}
In User Provider Dart File(State management) using ChangeNotifier:
//getter
void changeImageUrl(String imageUrl) {
_imageUrl = imageUrl;
notifyListeners();
}
//method
uploadPhotoToFirebase(File fileImage) async {
//used firebase user uid for path and File fileImage for File
try {
var imageUrl = await _storageService.uploadUserImage(fileImage, _userUid);
//assign downloadedUrl to getter
changeImageUrl(imageUrl);
} on PlatformException catch (e) {
print('Failed to pick image: $e');
}
}
User Profile Screen(UI):
//method for Image_picker
Future _pickPhotos(ImageSource imageSource) async {
final userProvider = Provider.of<UserProvider>(context, listen: false);
try {
final image = (await _picker.pickImage(source: imageSource));
if (image == null) return;
final imageTem = File(image.path);
//assigning File(String path) to firebase storage
await userProvider.uploadPhotoToFirebase(imageTem);
} on PlatformException catch (e) {
print('Failed to pick image: $e');
}
}
//Inside the build
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircleAvatar(
backgroundColor: darkGrey,
radius: 54.0,
//assign private String _imageUrl
child: _imageUrl != null
? ClipRRect(
borderRadius: BorderRadius.circular(50.0),
child: Image.network(
_imageUrl!,
width: 100,
height: 200,
fit: BoxFit.cover,
),
)
: Center(
child: Icon(Icons.person_rounded,
color: appColor, size: 80.0))),
TextButton(
style: ButtonStyle(
overlayColor: MaterialStateColor.resolveWith(
(states) => Colors.transparent)),
child: EditPhotoText(),
onPressed: () {
_pickPhotos(ImageSource.gallery);
/* Expecting this to change the local state to show
image preview but doesn't work.
*/
userProvider.changeImageUrl(_imageUrl!);
}),
Image is being stored in firebase and fetching image is working too. I am not able to figure out how to preview the image before uploading it to firebase. I think I have to use the set state to preview locally but I don't know how or is there a way I can do that through the provider package?

Why calling an async function whose defination is given in another program returns null or Future<type> instance for the first time as output?

Hello Im very to the flutter framework, so please let me know if im going wrong anywhere and the appropriate way of doing the things.
this is a drawerPage.dar file
In this file im trying to call a function getData for retrieving the data from firebase,this fucntion is in Database.dart file.
Database.dart
In the Database.dart file i wrote the getData function inside which im retrieving a particular record from the firebase and storing in a global variable. And then im trying to print the global variable in the drawerPage.dart file.But here when ever i run the program, for the first time the variable is having a null value and upon hot reload the actual value is getting stored in the variable.Please let me know how can i get rid of this problem.
output
drawerPageOutput
drawerPage.dart
import 'package:attendee/constants.dart';
import 'package:attendee/models/userdeails.dart';
import 'package:attendee/pages/profile.dart';
import 'package:attendee/services/authentication_service.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:attendee/services/database.dart';
import 'package:provider/provider.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:attendee/pages/userdetails.dart';
class StudentDashboard extends StatefulWidget {
#override
_StudentDashboardState createState() => _StudentDashboardState();
}
class _StudentDashboardState extends State<StudentDashboard> {
userdetails userdetail;
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
final AuthenticationService _auth = AuthenticationService();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
DatabaseService().getData('email');
final drawerHeader = UserAccountsDrawerHeader(
accountName: Text(userName),
accountEmail: Text('${result}'),
currentAccountPicture
: CircleAvatar(
child: FlutterLogo(size: 42.0),
backgroundColor: Colors.white,
);
final drawerItems = ListView(
children: <Widget>[
drawerHeader,
ListTile(
title: Row(
children: <Widget>[
Icon(Icons.perm_identity_outlined),
Text(' Profile'),
],
),
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (context)=>Profile())),
),
ListTile(
title: Text('To page 2'),
onTap: () => Navigator.of(context).push(_NewPage(2)),
),
ListTile(
title:Row(
children: <Widget>[
Icon(Icons.exit_to_app_rounded),
Text(' Logout'),
],
),
onTap: () async {
await _auth.signOut();
Navigator.of(context).pushNamed('/homepage');
},
),
],
);
return StreamProvider<List<userdetails>>.value(
value: DatabaseService().students,
initialData: [],
child: SafeArea(
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.lightGreen,
title: Text('Student Welcome'),
actions: <Widget>[
TextButton.icon(
onPressed: () async {
await _auth.signOut();
Navigator.of(context).pushNamed('/homepage');
},
icon: Icon(Icons.person),
label: Text('Logout'))
],
),
body:
UserDetails(),
drawer: GestureDetector(
onTap: display,
child: Drawer(
child: drawerItems,
),
),
),
),
);
}
display() async{
await DatabaseService().getData('email');
}
}
// <Null> means this route returns nothing.
class _NewPage extends MaterialPageRoute<Null> {
_NewPage(int id)
: super(builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page $id'),
elevation: 1.0,
),
body: Center(
child: Text('Page $id'),
),
);
});
}
database.dart
import 'package:attendee/models/userdeails.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_icons/flutter_icons.dart';
import '../constants.dart';
class DatabaseService{
final String uid;
DatabaseService({this.uid});
//collection reference
final CollectionReference user_details=FirebaseFirestore.instance.collection('users');`
final CollectionReference tutor_details` `=FirebaseFirestore.instance.collection("tutors");`
Future updateStudentData(String fullname,String mobilenumber,String `email,String rollno,String tutorid,String role) async {`
return await user_details.doc(uid).set({
'fullname' : fullname,
'mobilenumber': mobilenumber,
'email' : email,
'rollno': rollno,
'tutorid': tutorid,
'role' : role,//FMERT series
});
}
Future updateTutorData(String fullname,String mobilenumber,String `email,String rollno,String tutorid,String role) async {`
return await tutor_details.doc(uid).set({
'fullname' : fullname,
'mobilenumber': mobilenumber,
'email' : email,
'rollno': rollno,
'tutorid': tutorid,
'role' : role,//FMERT series
});
}
//studentDetails from snapshot
List<userdetails> _studentDetailsFromSnapshot(QuerySnapshot snapshot){
return snapshot.docs.map((doc){
return userdetails(
fullname: doc.data()['fullname'] ?? '',
mobilenumber: doc.data()['mobilenumber'] ?? '',
email: doc.data()['email'] ?? '',
rollno: doc.data()['rollno'] ?? '',
tutorid: doc.data()['tutorid'] ?? '',
//role: doc.data()['role'] ?? '',
);
}).toList();
}
//get students stream
Stream<List<userdetails>> get students {
return user_details.snapshots()
.map(_studentDetailsFromSnapshot);
}
//tutorsDetails from snapshot
List<userdetails> _tutorDetailsFromSnapshot(QuerySnapshot snapshot){
return snapshot.docs.map((doc){
return userdetails(
fullname: doc.data()['fullname'] ?? '',
mobilenumber: doc.data()['mobilenumber'] ?? '',
email: doc.data()['email'] ?? '',
rollno: doc.data()['rollno'] ?? '',
tutorid: doc.data()['tutorid'] ?? '',
);
}).toList();
}
//get tutors stream
Stream<List<userdetails>> get tutors {
return user_details.snapshots()
.map(_studentDetailsFromSnapshot);
}
void display() {
tutor_details.get().then((querySnapshot) {
querySnapshot.docs.forEach((result) {
print(result.data());
});
});
}
getData (String string) async{
String userId = await FirebaseAuth.instance.currentUser.uid;
final document = isTutor ? `FirebaseFirestore.instance.doc('tutors/$userId') :`
await FirebaseFirestore.instance.doc('users/$userId');
document.get().then((DocumentSnapshot) async {
if(string =='role') {
checkRole = DocumentSnapshot.data()[string].toString();
print('$checkRole inside getData Function');
//return checkRole;
print(checkRole);
}
else {
print(result);
result = await DocumentSnapshot.data()[string].toString();
print('${DocumentSnapshot.data()[string].toString()} in the `database else block');`
//return result;
}
//print(document("name"));
});
}
}
After changes
terminaloutput
draweroutput
""when ever i run the program, for the first time the variable is having a null value and upon hot reload the actual value is getting stored in the variable""
When we try to get data from http / https request, it takes some time. Meanwhile the page gets loaded and you get null values.
You can use Provider package to resolve this issue, or try the below code. Please add the below code in your drawerPage.dart.
What I have done below is made getData() return type. Only on receiving a value from this function, _loadOnce will change to false & final screen will be shown.
Database.dart
Future<bool> getData (String string) async{
String userId = await FirebaseAuth.instance.currentUser.uid;
final document = isTutor ? `FirebaseFirestore.instance.doc('tutors/$userId') :`
await FirebaseFirestore.instance.doc('users/$userId');
document.get().then((DocumentSnapshot) async {
if(string =='role') {
checkRole = DocumentSnapshot.data()[string].toString();
print('$checkRole inside getData Function');
//return checkRole;
print(checkRole);
return true;
}
else {
print(result);
result = await DocumentSnapshot.data()[string].toString();
print('${DocumentSnapshot.data()[string].toString()} in the `database else block');`
//return result;
return false;
}
//print(document("name"));
});
}
}
/// create a new variable.
bool _loadOnce = true;
/// shift your code `DatabaseService().getData('email');`
#override
void didChangeDependencies() {
if(_loadOnce == true) {
DatabaseService().getData('email').then((value) {
if(value == true){
setState(() {
_loadOnce = false;
});
} else {
/// you can write your code here
setState(() {
_loadOnce = false;
});
}
)}
}
super.didChangeDependencies();
}
Below code will show a spinner till the time all the code gets executed and values are retreived.
/// in your main page under Scaffold
body: _loadOnce == true
? Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).primaryColor,
),
)
: UserDetails(),

How to upload image to Firebase Storage and automatically store it in Cloud Firestore?

I'm currently stuck at where I'm trying to upload image to Firestore Storage and automatically store the image URL to Cloud Firestore. I have tried to manually upload the image to Firebase Storage and pasting the image url to Cloud Firestore then retrieving it to show the image in my app and it works. Heres the coding that I have done so far:
models/enter.dart
class Enter {
final String enterId;
final String enter;
final String price;
final String url;
Enter({this.enter, this.price, #required this.enterId, this.url});
factory Enter.fromJson(Map<String, dynamic> json){
return Enter(
enter: json['enter'],
price: json['price'],
url: json['url'],
enterId: json['enterId']
);
}
Map<String,dynamic> toMap(){
return {
'enter':enter,
'price':price,
'url':url,
'enterId':enterId
};
}
}
services/firestore_service2
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:journal_app/src/models/enter.dart';
class FirestoreService2 {
FirebaseFirestore _db = FirebaseFirestore.instance;
//Get Entries
Stream<List<Enter>> getEnter(){
return _db
.collection('enters')
.snapshots()
.map((snapshot) => snapshot.docs
.map((doc) => Enter.fromJson(doc.data()))
.toList());
}
//Upsert
Future<void> setEnter(Enter enter){
var options = SetOptions(merge:true);
return _db
.collection('enters')
.doc(enter.enterId)
.set(enter.toMap(),options);
}
//Delete
Future<void> removeEnter(String enterId){
return _db
.collection('enters')
.doc(enterId)
.delete();
}
}
provider/enter_provider.dart
import 'package:flutter/material.dart';
import 'package:journal_app/src/models/enter.dart';
import 'package:journal_app/src/services/firestore_service2.dart';
import 'package:uuid/uuid.dart';
class EnterProvider with ChangeNotifier {
final firestoreService = FirestoreService2();
String _enter;
String _price;
String _enterId;
String _url;
var uuid = Uuid();
//Getters
String get enter => _enter;
String get price => _price;
String get url => _url;
Stream<List<Enter>> get enters => firestoreService.getEnter();
//Setters
set changeEnter(String enter){
_enter = enter;
notifyListeners();
}
set changePrice(String price){
_price = price;
notifyListeners();
}
set changeUrl(String url){
_url = url;
notifyListeners();
}
//Functions
loadAll(Enter enter){
if (enter != null && price != null){
_enter =enter.enter;
_price =enter.price;
_url=enter.url;
_enterId = enter.enterId;
} else {
_enter = null;
_price = null;
_url = null;
_enterId = null;
}
}
saveEnter(){
if (_enterId == null){
//Add
var newEnter = Enter(enter: _enter, price: _price, url: _url, enterId: uuid.v1());
print(newEnter.enter);
print(newEnter.price);
print(newEnter.url);
firestoreService.setEnter(newEnter);
} else {
//Edit
var updatedEnter = Enter(enter: _enter, price: _price, url: _url, enterId: _enterId);
firestoreService.setEnter(updatedEnter);
}
}
removeEnter(String enterId){
firestoreService.removeEnter(enterId);
}
}
screens/enter.dart where user insert product name, price and image
import 'package:date_format/date_format.dart';
import 'package:flutter/material.dart';
import 'package:journal_app/src/models/enter.dart';
import 'package:journal_app/src/providers/enter_provider.dart';
import 'package:provider/provider.dart';
class EnterScreen extends StatefulWidget {
final Enter enter;
final Enter price;
final Enter category;
EnterScreen({this.enter, this.price, this.category});
#override
_EnterScreenState createState() => _EnterScreenState();
}
class _EnterScreenState extends State<EnterScreen> {
final enterController = TextEditingController();
final enterController2 = TextEditingController();
#override
void dispose() {
enterController.dispose();
enterController2.dispose();
super.dispose();
}
#override
void initState() {
final enterProvider = Provider.of<EnterProvider>(context,listen: false);
if (widget.enter != null){
//Edit
enterController.text = widget.enter.enter;
enterController2.text = widget.enter.price;
enterProvider.loadAll(widget.enter);
enterProvider.loadAll(widget.price);
} else {
//Add
enterProvider.loadAll(null);
}
super.initState();
}
#override
Widget build(BuildContext context) {
final enterProvider = Provider.of<EnterProvider>(context);
return Scaffold(
appBar: AppBar(title: Text('Products')),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: [
TextField(
decoration: InputDecoration(
labelText: 'Product Name', border: InputBorder.none,
),
style: TextStyle(color: Colors.black, fontSize: 25),
maxLines: 5,
minLines: 2,
onChanged: (String value) => enterProvider.changeEnter = value,
controller: enterController,
),
TextField(
decoration: InputDecoration(
labelText: 'Product Price', border: InputBorder.none,
),
style: TextStyle(color: Colors.black, fontSize: 25),
maxLines: 5,
minLines: 2,
onChanged: (String value) => enterProvider.changePrice = value,
controller: enterController2,
),
RaisedButton(
color: Theme.of(context).accentColor,
child: Text('Save',style: TextStyle(color: Colors.white, fontSize: 20)),
onPressed: () {
enterProvider.saveEnter();
Navigator.of(context).pop();
},
),
(widget.enter != null) ? RaisedButton(
color: Colors.red,
child: Text('Delete',style: TextStyle(color: Colors.white, fontSize: 20)),
onPressed: () {
enterProvider.removeEnter(widget.enter.enterId);
Navigator.of(context).pop();
},
): Container(),
],
),
),
);
}
}
screens/product.dart where this screen show the product name, price and image in listview
import 'package:date_format/date_format.dart';
import 'package:flutter/material.dart';
import 'package:journal_app/src/models/enter.dart';
import 'package:journal_app/src/providers/enter_provider.dart';
import 'package:journal_app/src/screens/enter.dart';
import 'package:provider/provider.dart';
class ProductScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final enterProvider = Provider.of<EnterProvider>(context);
return Scaffold(
appBar: AppBar(
title: Text('Products'),
),
body: StreamBuilder<List<Enter>>(
stream: enterProvider.enters,
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
leading: Image.network(
snapshot.data[index].url,
width: 100,
height: 100,
fit: BoxFit.fitWidth,
),
trailing:
Icon(Icons.edit, color: Theme.of(context).accentColor),
title: Text(
snapshot.data[index].enter, style: TextStyle(fontSize: 25),
),
subtitle: Text(
snapshot.data[index].price,
),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
EnterScreen(enter: snapshot.data[index], price: snapshot.data[index])));
},
);
});
}),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => EnterScreen()));
},
),
);
}
}
The goal is to upload image to Firebase Storage, storing the image url at the same time when uploading new image.
I need advice for this project.
In order to upload image to Firebase Storage & save it in Cloud Firestore, you need to do the following:
Step 1: Get the image that you want to upload
For this, you can use the image_picker package
It will give you a PickedFile object (let's say it pickedFile). Convert this object to a File object by using this code:
final file = File(pickedFile.path);
Step 2: Upload this file to Firebase Storage:
Give the image that you want to upload a unique name.
final imageName = '${DateTime.now().millisecondsSinceEpoch}.png';
Create Firebase Storage Reference:
final firebaseStorageRef = FirebaseStorage.instance
.ref()
.child('images/$imageName'); // This will create a images directory in Firebase storage & save your image in that directory
Start uploading image:
final uploadTask = firebaseStorageRef.putFile(file);
final taskSnapshot = await uploadTask.onComplete;
Get the image URL:
final _fileURL = await taskSnapshot.ref.getDownloadURL();
Save this image URL in Cloud Firestore.
// This will save the image in "images" collection & update the "uploadedImage" value of document with id the same as the value of "id" variable.
await FirebaseFirestore.instance.collection(images).doc(id).update({'uploadedImage': _fileURL});
If you are updating an existing object then use the update method or else you can use the set method.
In your case you must follow bellow steps
First upload your image to Firebase Storage and get the download URL
Now you have the download URL and you can Upload your Enter object to Cloud FireStore with the url
Bellow method shows how could you store the image and get the download url list
Future<String> uploadImage(var imageFile ) async {
StorageReference ref = storage.ref().child("/photo.jpg");
StorageUploadTask uploadTask = ref.putFile(imageFile);
var dowurl = await (await uploadTask.onComplete).ref.getDownloadURL();
url = dowurl.toString();
return url;
}

flutter validate form asynchronously

new TextFormField(
validator: (value) async{
if (value.isEmpty) {
return 'Username is required.';
}
if (await checkUser()) {
return 'Username is already taken.';
}
},
controller: userNameController,
decoration: InputDecoration(hintText: 'Username'),
),
I have a form for user, and I want to check if the user already exists in the firestore datebase.
Future checkUser() async {
var user = await Firestore.instance
.collection('users')
.document(userNameController.text)
.get();
return user.exists;
}
This is my function to check if the user document already exists in the database.
But validator gives me this error.
[dart] The argument type '(String) → Future' can't be assigned to the parameter type '(String) → String'.
How should I fix this issue?
At this time I think that you can't associate a Future to a validator.
What you can do is this verifying the data on a button click or in another way and set the state on the validator response var.
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: Form(
key: _formKey,
child: Column(children: [
new TextFormField(
validator: (value) {
return usernameValidator;
},
decoration: InputDecoration(hintText: 'Username')),
RaisedButton(
onPressed: () async {
var response = await checkUser();
setState(() {
this.usernameValidator = response;
});
if (_formKey.currentState.validate()) {}
},
child: Text('Submit'),
)
])));
}
I needed to do this for username validation recently (to check if a username already exists in firebase) and this is how I achieved async validation on a TextFormField ( without installation of any additional packages). I have a "users" collection where the document name is the unique username ( Firebase can't have duplicate document names in a collection but watch out for case sensitivity)
//In my state class
class _MyFormState extends State<MyForm> {
final _usernameFormFieldKey = GlobalKey<FormFieldState>();
//Create a focus node
FocusNode _usernameFocusNode;
//Create a controller
final TextEditingController _usernameController = new TextEditingController();
bool _isUsernameTaken = false;
String _usernameErrorString;
#override
void initState() {
super.initState();
_usernameFocusNode = FocusNode();
//set up focus node listeners
_usernameFocusNode.addListener(_onUsernameFocusChange);
}
#override
void dispose() {
_usernameFocusNode.dispose();
_usernameController.dispose();
super.dispose();
}
}
Then in my TextFormField widget
TextFormField(
keyboardType: TextInputType.text,
focusNode: _usernameFocusNode,
textInputAction: TextInputAction.next,
controller: _usernameController,
key: _usernameFormFieldKey,
onEditingComplete: _usernameEditingComplete,
validator: (value) => _isUsernameTaken ? "Username already taken" : _usernameErrorString,)
Listen for focus changes on the widget i.e when it loses focus. You can also do something similar for "onEditingComplete" method
void _onUsernameFocusChange() {
if (!_usernameFocusNode.hasFocus) {
String message = UsernameValidator.validate(_usernameController.text.trim());
//First make sure username is in valid format, if it is then check firebase
if (message == null) {
Firestore.instance.collection("my_users").document(_usernameController.text.trim()).get().then((doc) {
if (doc.exists) {
setState(() {
_isUsernameTaken = true;
_usernameErrorString = null;
});
} else {
setState(() {
_isUsernameTaken = false;
_usernameErrorString = null;
});
}
_usernameFormFieldKey.currentState.validate();
}).catchError((onError) {
setState(() {
_isUsernameTaken = false;
_usernameErrorString = "Having trouble verifying username. Please try again";
});
_usernameFormFieldKey.currentState.validate();
});
} else {
setState(() {
_usernameErrorString = message;
});
_usernameFormFieldKey.currentState.validate();
}
}
}
For completeness, this is my username validator class
class UsernameValidator {
static String validate(String value) {
final regexUsername = RegExp(r"^[a-zA-Z0-9_]{3,20}$");
String trimmedValue = value.trim();
if (trimmedValue.isEmpty) {
return "Username can't be empty";
}
if (trimmedValue.length < 3) {
return "Username min is 3 characters";
}
if (!regexUsername.hasMatch(trimmedValue)) {
return "Usernames should be a maximum of 20 characters with letters, numbers or underscores only. Thanks!";
}
return null;
}
}
I had the same problem while using Firebase's Realtime Database but I found a pretty good solution similar to Zroq's solution. This function creates a simple popup form to have the user input a name. Essentially, I was trying to see if a particular name for a specific user was already in the database and show a validation error if true. I created a local variable called 'duplicate' that is changed anytime the user clicks the ok button to finish. Then I can call the validator again if there is an error, and the validator will display it.
void add(BuildContext context, String email) {
String _name;
bool duplicate = false;
showDialog(
context: context,
builder: (_) {
final key = GlobalKey<FormState>();
return GestureDetector(
onTap: () => FocusScope.of(context).requestFocus(new FocusNode()),
child: AlertDialog(
title: Text("Add a Workspace"),
content: Form(
key: key,
child: TextFormField(
autocorrect: true,
autofocus: false,
decoration: const InputDecoration(
labelText: 'Title',
),
enableInteractiveSelection: true,
textCapitalization: TextCapitalization.sentences,
onSaved: (value) => _name = value.trim(),
validator: (value) {
final validCharacters =
RegExp(r'^[a-zA-Z0-9]+( [a-zA-Z0-9]+)*$');
if (!validCharacters.hasMatch(value.trim())) {
return 'Alphanumeric characters only.';
} else if (duplicate) {
return 'Workspace already exists for this user';
}
return null;
},
),
),
actions: <Widget>[
FlatButton(
child: const Text("Ok"),
onPressed: () async {
duplicate = false;
if (key.currentState.validate()) {
key.currentState.save();
if (await addToDatabase(_name, email) == false) {
duplicate = true;
key.currentState.validate();
} else {
Navigator.of(context).pop(true);
}
}
},
),
FlatButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop(false);
},
),
],
),
);
});
}

Resources