I'm working on authentication/signing up with FirebaseStorage where the user can upload an image during sign-up but I keep on getting errors about the user not having any permission. When the user clicks sign-up, the sign-up process using Firebase works because the user is created in Authentication but the information does not get saved on the Firestore Database. Does anyone know what I'm doing wrong?
This is my Firebase Storage rule:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /user-images {
allow read, create: if request.auth != null;
}
match /avatar.png {
allow read: if true;
}
}
}
This is the sign-up screen.
import 'dart:io';
import 'dart:ui';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/services.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '/screens/auth_screen.dart';
import '/widgets/build_row.dart';
import '/screens/chat_screen.dart';
import '/widgets/user_image_picker.dart';
class SignUpScreen extends StatefulWidget {
static const routeName = '/sign-up-screen';
@override
_SignUpScreenState createState() => _SignUpScreenState();
}
class _SignUpScreenState extends State<SignUpScreen> {
final _auth = FirebaseAuth.instance;
final GlobalKey<FormState> _key = GlobalKey();
String _username = '';
String _userPW = '';
String _userEmail = '';
var _isLoading = false;
File? _userImageFile;
void _pickedImage(File image) {
_userImageFile = image;
}
void _submit() {
final isValid = _key.currentState!.validate();
if (isValid) {
_key.currentState!.save();
}
this._submitAuthForm(_userEmail.trim(), _userPW.trim(), _username.trim());
}
void _submitAuthForm(
String userEmail,
String password,
String username,
) async {
try {
setState(() {
_isLoading = true;
});
UserCredential userCredential = await _auth
.createUserWithEmailAndPassword(email: userEmail, password: password);
final ref = FirebaseStorage.instance
.ref()
.child('user-images/${userCredential.user!.uid}.jpg');
final avatarRef = FirebaseStorage.instance.ref().child('avatar.png');
var url;
if (_userImageFile == null) {
url = await avatarRef.getDownloadURL();
}
if (_userImageFile != null) {
await ref.putFile(_userImageFile!);
url = await ref.getDownloadURL();
}
await FirebaseFirestore.instance
.collection('users')
.doc(userCredential.user!.uid)
.set(
{
'username': username,
'email': userEmail,
'image_url': url,
},
);
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (ctx) => ChatScreen()),
);
} on PlatformException catch (error) {
var message = 'An Error has occured!';
if (error.message != null) {
message = error.message!;
}
setState(() {
_isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.deepPurple.shade600,
),
);
} catch (error) {
if (error.toString().contains(
'The email address is already in use by another account.')) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text('The email address is already in use by another account.'),
backgroundColor: Colors.deepPurple.shade600,
),
);
}
print(error);
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
return GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: deviceSize.width * 0.8,
constraints: BoxConstraints(
maxHeight: (deviceSize.height -
MediaQuery.of(context).viewInsets.bottom) *
0.7),
decoration: BoxDecoration(
border: Border.all(
color: Colors.deepPurple.shade600,
width: 2.0,
),
color: Colors.white,
),
child: SingleChildScrollView(
padding: EdgeInsets.only(
left: 10,
bottom: 20,
),
child: Form(
key: _key,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
UserImagePicker(_pickedImage),
BuildRowWithIcon(Icons.person, 'Username:', false,
(value) {
if (value.isEmpty) {
return 'Please enter a username';
}
return null;
}, (value) {
_username = value;
}, 'Username must be at least 6 characters long. Special characters: \$, ^, &, !, ?'),
BuildRow(
Icons.email_outlined,
'E-mail:',
false,
(value) {
if (value.isEmpty) {
return 'Please provide an e-mail';
}
return null;
},
(value) {
_userEmail = value;
},
),
BuildRowWithIcon(
Icons.password_outlined, 'Password:', true,
(value) {
if (value.isEmpty) {
return 'Please enter a password';
}
if (value.length < 7) {
return 'Password must be at least 7 characters long';
}
return null;
}, (value) {
_userPW = value;
}, 'Password must be at least 7 characters long. You must use a combination of two of the following: letters, numbers, & special characters. Special characters: \$, ^, &, !, ?'),
BuildRowWithIcon(
Icons.password_outlined,
'Confirm PW:',
true,
(value) {
if (value.isEmpty) {
return 'Please enter the same password';
}
return null;
},
null,
'Confirm password',
),
],
),
),
),
),
const SizedBox(height: 30),
if (_isLoading) CircularProgressIndicator(),
if (!_isLoading)
ElevatedButton(
onPressed: _submit,
child: const Text(
'Sign up!',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
style: ButtonStyle(
elevation: MaterialStateProperty.all<double>(20),
backgroundColor: MaterialStateProperty.all<Color>(
Colors.deepPurple.shade600),
foregroundColor: MaterialStateProperty.all<Color>(
Colors.tealAccent.shade200),
padding: MaterialStateProperty.all<EdgeInsets>(
EdgeInsets.all(15)),
shadowColor: MaterialStateProperty.all<Color>(
Colors.deepPurple.shade800),
shape: MaterialStateProperty.all<OutlinedBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
)),
),
),
SizedBox(height: 25),
TextButton(
onPressed: () => Navigator.of(context)
.pushReplacementNamed(AuthScreen.routeName),
child: Text(
'I already have an account.',
style: TextStyle(
color: Colors.deepPurple.shade400,
fontSize: 15,
),
),
),
],
),
),
),
);
}
}
And this is for the ImagePicker() widget
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart' as syspath;
class UserImagePicker extends StatefulWidget {
final void Function(File pickedImage) imagePickFn;
UserImagePicker(this.imagePickFn);
@override
_UserImagePickerState createState() => _UserImagePickerState();
}
class _UserImagePickerState extends State<UserImagePicker> {
File? _pickedImage;
Future<void> _pickImage() async {
final _pickedImageXFile =
await ImagePicker().pickImage(
source: ImageSource.gallery,
imageQuality: 50,
maxHeight: 150,
maxWidth: 150);
if (_pickedImageXFile == null) {
return;
}
final _pickedImageFile = File(_pickedImageXFile.path);
setState(() {
_pickedImage = _pickedImageFile;
});
final appDir = await syspath.getApplicationDocumentsDirectory();
final fileName = path.basename(_pickedImageFile.path);
final userImageFile =
await _pickedImageFile.copy('${appDir.path}/$fileName');
widget.imagePickFn(userImageFile);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
margin: EdgeInsets.only(top: 10),
child: CircleAvatar(
radius: 35,
backgroundColor: Colors.tealAccent.shade200,
backgroundImage:
_pickedImage == null ? null : FileImage(_pickedImage!),
),
),
TextButton.icon(
onPressed: _pickImage,
icon: Icon(
Icons.image_outlined,
color: Colors.deepPurple.shade600,
),
label: Text(
'Add User Image',
style: TextStyle(color: Colors.deepPurple.shade600),
),
),
],
);
}
}
Side question: Is there a way to use Future with validation? I want to be able to show the user while he/she is typing in password/confirm password that it doesn't match the requirements or that the confirm password value doesn't the password value. Do I need to use separate TextControllers for this?
CodePudding user response:
The match /user-images
in your rules matches a file in the root names user-images
. It does not recursively apply the permissions to files in a filter.
To allow anyone to write files under /user-images
, you can apply a wildcard match:
match /user-images/{file} {
allow read, create: if request.auth != null;
}
Or if you want to allow writing in any folder under /user-images
:
match /user-images/{file=**} {
allow read, create: if request.auth != null;
}
To then allow everyone to read /user-images/avatar.png
:
match /user-images/avatar.png {
allow read: if true;
}
CodePudding user response:
You need to check the permission in rules section and allow read, write: if true;
and also check AndroidManifest.xml file, if you have given the required permission or not.