# #firebase #flutter #firebase-storage
Вопрос:
Я работаю над аутентификацией/регистрацией в FirebaseStorage, где пользователь может загружать изображение во время регистрации, но я продолжаю получать ошибки о том, что у пользователя нет никаких разрешений. Когда пользователь нажимает «Зарегистрироваться», процесс регистрации с использованием Firebase работает, поскольку пользователь создается при проверке подлинности, но информация не сохраняется в базе данных Firestore. Кто-нибудь знает, что я делаю не так?
Это мое правило хранения Firebase:
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;
}
}
}
Это экран регистрации.
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: $, ^, amp;, !, ?'),
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, amp; special characters. Special characters: $, ^, amp;, !, ?'),
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,
),
),
),
],
),
),
),
);
}
}
И это для виджета ImagePicker()
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),
),
),
],
);
}
}
Побочный вопрос:
Есть ли способ использовать будущее с проверкой? Я хочу иметь возможность показать пользователю, когда он/она вводит пароль/подтверждает пароль, что он не соответствует требованиям или что значение подтверждения пароля не соответствует значению пароля. Нужно ли для этого использовать отдельные текстовые контроллеры?
Ответ №1:
match /user-images
В ваших правилах файл соответствует корневым именам user-images
. Он не рекурсивно применяет разрешения к файлам в фильтре.
Чтобы разрешить кому-либо записывать файлы под /user-images
, вы можете применить соответствие подстановочным знакам:
match /user-images/{file} {
allow read, create: if request.auth != null;
}
Или если вы хотите разрешить запись в любую папку в разделе /user-images
:
match /user-images/{file=**} {
allow read, create: if request.auth != null;
}
Чтобы затем позволить всем желающим прочитать /user-images/avatar.png
:
match /user-images/avatar.png {
allow read: if true;
}
Комментарии:
1. Это решило проблему так быстро! И подумать только, я потратил кучу времени на настройку правил после «разрешить», а не части «совпадение»… большое вам спасибо!!
Ответ №2:
Вам нужно проверить разрешение в разделе «Правила» и разрешить чтение, запись: если верно;
а также проверьте AndroidManifest.xml файл, если вы дали необходимое разрешение или нет.