Загрузка изображения в хранилище FirebaseStorage — Отказано в разрешении

# #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 файл, если вы дали необходимое разрешение или нет.