1. 회원 가입 시, 이미지 업로드를 해보자.
먼저 파베 사이트에서 Storage부분에 접속 한 후, 아래와 같이 rule을 설정해보자. (해당 룰은 로그인 한 유저는 모든 접근이 가능하다는 룰임). 기타 다른 룰들을 설정해보려면, 문서보기를 눌러서 더 내용을 확인해보자.
그리고 firestore database부분에도 위와 같이 룰을 적용시켜보자. firestore에는 구글 클라우드로 들어가서 native로 변경해주어야 한다.
2. 패키지 설치
flutter pub add firebase_storage --> 파일 자체 저장용 (아마존 s3 같은거)
flutter pub add image_picker
flutter pub add cloud_firestore --> db같이 data 저장용 : url 주소를 저장하기 위해..
위 3가지 패키지를 설치하자.
3. app/build.gradle 수정
defaultConfig {
minSdkVersion 19
multiDexEnabled true
}
위 2개를 추가해주자.
- auth.dart
import 'dart:io';
import 'package:chat_app/widgets/user_image_picker.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
final _firebase = FirebaseAuth.instance;
class AuthScreen extends StatefulWidget {
const AuthScreen({super.key});
@override
State<AuthScreen> createState() {
return _AuthScreenState();
}
}
class _AuthScreenState extends State<AuthScreen> {
final _form = GlobalKey<FormState>();
var _isLogin = true;
var _enteredEmail = '';
var _enteredPassword = '';
var _enteredUsername = '';
File? _selectedImage;
var _isAuthenticating = false;
void _submit() async {
final isValid = _form.currentState!.validate();
if (!isValid || !_isLogin && _selectedImage == null) {
// show error message ...
return;
}
_form.currentState!.save();
try {
setState(() {
_isAuthenticating = true;
});
if (_isLogin) {
final userCredentials = await _firebase.signInWithEmailAndPassword(
email: _enteredEmail, password: _enteredPassword);
} else {
final userCredentials = await _firebase.createUserWithEmailAndPassword(
email: _enteredEmail, password: _enteredPassword);
final storageRef = FirebaseStorage.instance
.ref()
.child('user_images')
.child('${userCredentials.user!.uid}.jpg');
await storageRef.putFile(_selectedImage!);
final imageUrl = await storageRef.getDownloadURL();
await FirebaseFirestore.instance
.collection('users')
.doc(userCredentials.user!.uid)
.set({
'username': _enteredUsername,
'email': _enteredEmail,
'image_url': imageUrl,
});
}
} on FirebaseAuthException catch (error) {
if (error.code == 'email-already-in-use') {
// ...
}
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(error.message ?? 'Authentication failed.'),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.primary,
body: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
margin: const EdgeInsets.only(
top: 30,
bottom: 20,
left: 20,
right: 20,
),
width: 200,
child: Image.asset('assets/images/chat.png'),
),
Card(
margin: const EdgeInsets.all(20),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Form(
key: _form,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!_isLogin)
UserImagePicker(
onPickImage: (pickedImage) {
_selectedImage = pickedImage;
},
),
TextFormField(
decoration: const InputDecoration(
labelText: 'Email Address'),
keyboardType: TextInputType.emailAddress,
autocorrect: false,
textCapitalization: TextCapitalization.none,
validator: (value) {
if (value == null ||
value.trim().isEmpty ||
!value.contains('@')) {
return 'Please enter a valid email address.';
}
return null;
},
onSaved: (value) {
_enteredEmail = value!;
},
),
if (!_isLogin)
TextFormField(
decoration:
const InputDecoration(labelText: 'Username'),
enableSuggestions: false,
validator: (value) {
if (value == null ||
value.isEmpty ||
value.trim().length < 4) {
return 'Please enter at least 4 characters.';
}
return null;
},
onSaved: (value) {
_enteredUsername = value!;
},
),
TextFormField(
decoration:
const InputDecoration(labelText: 'Password'),
obscureText: true,
validator: (value) {
if (value == null || value.trim().length < 6) {
return 'Password must be at least 6 characters long.';
}
return null;
},
onSaved: (value) {
_enteredPassword = value!;
},
),
const SizedBox(height: 12),
if (_isAuthenticating)
const CircularProgressIndicator(),
if (!_isAuthenticating)
ElevatedButton(
onPressed: _submit,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context)
.colorScheme
.primaryContainer,
),
child: Text(_isLogin ? 'Login' : 'Signup'),
),
if (!_isAuthenticating)
TextButton(
onPressed: () {
setState(() {
_isLogin = !_isLogin;
});
},
child: Text(_isLogin
? 'Create an account'
: 'I already have an account'),
),
],
),
),
),
),
),
],
),
),
),
);
}
}
1) 기본적으로 로그인 화면과 회원가입 화면 두개로 나뉜다.
2) textformfield 부분에 정보를 넣고 유효성 검사로직을 넣는다. 여기서 submit이 되면 에러메시지가 2가지 종류가 있는데 1가지는 내가 매뉴얼하게 에러메시지 넣은거는 해당 폼 하단에 나오고, 파베에러 (중복 아이디 등)는 스낵바로 나오게 해놨다.
3) submit 이후 기본 _isAuthenticating이 false로 되어있는거를 true로 바꿔줌으로써 로딩중이 표시되게끔 만들어주었다.
그리고, 유효성 검사가 false이거나, 회원가입화면에서 이미지 등록을 안하면 null 처리해주었다.
위 로직이 문제가없으면 각 텍스트폼필드의 save가 발동되고, 파베와 통신(회원가입 or 로그인)을 시작한다.
회원가입 부분을 보자면, 파베스토리지에 선택된 이미지를 넣고, url 을 받은 후, 해당 url정보와 기타 정보를
파베파이어스토어에 저장하는 로직이다.
회원가입 후, 파이어스토어에 저장은 아래와 같이 등록된다.
- widgets/user_image_picker.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class UserImagePicker extends StatefulWidget {
const UserImagePicker({
super.key,
required this.onPickImage,
});
final void Function(File pickedImage) onPickImage;
@override
State<UserImagePicker> createState() {
return _UserImagePickerState();
}
}
class _UserImagePickerState extends State<UserImagePicker> {
File? _pickedImageFile;
void _pickImage() async {
final pickedImage = await ImagePicker().pickImage(
source: ImageSource.camera,
imageQuality: 50,
maxWidth: 150,
);
if (pickedImage == null) {
return;
}
setState(() {
_pickedImageFile = File(pickedImage.path);
});
widget.onPickImage(_pickedImageFile!);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
CircleAvatar(
radius: 40,
backgroundColor: Colors.grey,
foregroundImage:
_pickedImageFile != null ? FileImage(_pickedImageFile!) : null,
),
TextButton.icon(
onPressed: _pickImage,
icon: const Icon(Icons.image),
label: Text(
'Add Image',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
)
],
);
}
}
1) auth.dart에 사용하는 이미지 관련 위젯이다.
-결과 화면