일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- MS-SQL
- MVVM
- GitHub
- 함수
- page
- 리엑트
- 플러터
- listview
- JavaScript
- 바인딩
- MSSQL
- spring boot
- 닷넷
- db
- HTML
- 애니메이션
- Flutter
- AnimationController
- 자바스크립트
- 오류
- Firebase
- typescript
- Maui
- Binding
- 마우이
- React JS
- 깃허브
- 파이어베이스
- Animation
- .NET
Archives
- Today
- Total
개발노트
49. [Flutter] Firebase Authentication (이메일/비밀번호) 인증 구현하기 본문
반응형
Flutter 앱에 Firebase Authentication 이메일/비밀번호 로그인 인증 추가하기
- 주요 기능들.
- User 생성하기 (이메일, 패스워드)
- Future<UserCredentialPlatform> createUserWithEmailAndPassword(String email, String password)
- User Sign-In 기능 (로그인)
- Future<UserCredential> signInWithEmailAndPassword({required String email, required String password})
- User Sign-Out 기능 (로그아웃)
- Future<void> signOut()
위와 같이 크게 3가지 기능을 구현하고, MVVM 패턴에 적용할 수 있도록 예시를 작성해보겠습니다.
우선 아래 포스팅으로 Firebase를 연동하고, Authenication 기능을 추가하여 Flutter 프로젝트 환경을 만들어줍니다.
1. Flutter 앱에 Firebase 연동하는방법: https://mroh1226.tistory.com/153
2. Firebase Authenication 기능 추가하기: https://mroh1226.tistory.com/155
3. main 함수에 Firebase 추기화 해주기
main.dart
void main() async {
...
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
...
}
구현할 기능, 화면 모습
- 로그인, 로그아웃, 회원가입 3가지 기능을 가진 버튼과 이메일, 비밀번호를 입력받을 수 있는 텍스트 필드를 구현
- 이메일/비밀번호 방식을 통해 회원가입 버튼을 클릭, User Authenication을 추가해줍니다.
- 추가된 계정은 Firebase Console에 바로 추가됩니다.
- 로그인으로 Sign In, 로그아웃으로 Sign Out 할 수 있습니다.
예시 설명.
이 소스 코드는 Flutter를 사용하여 Firebase Authentication을 통한 이메일 및 비밀번호 인증을 처리하는 앱의 일부입니다. 아래에 각 클래스와 주요 소스 코드에 대한 설명을 제공합니다.
1. AuthRepository 클래스:
- Firebase의 Authentication을 다루기 위한 클래스입니다.
- get user 메서드: 현재 로그인된 사용자를 가져옵니다.
- get isLoggedIn 메서드: 사용자가 로그인되어 있는지 여부를 확인합니다.
- authStateChange 메서드: 사용자 상태의 변화를 스트림으로 반환합니다.
- createUser, userSignOut, userSignIn 메서드: 사용자 생성, 로그아웃, 로그인 기능을 제공합니다.
2. Providers:
- authRepoProvider: **AuthRepository**의 인스턴스를 생성하는 Provider입니다.
- authStreamProvider: **AuthRepository**의 **authStateChange**를 사용하여 사용자 인증 상태 변화를 관찰하는 Provider입니다.
AuthRepository (Repository)
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
////Firebase///////////////////////////////////////////////////////////////////////
class AuthRepository {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
//Firebase의 Authentication을 다룰 수 있도록 FirebaseAuth 인스턴스를 생성함
User? get user => _firebaseAuth.currentUser; //get 정의하는 방법 중 하나
bool get isLoggedIn => user != null;
//_firebaseAuth.currentUser: Firebase에 currentUser가 있는지? 유무로 로그인 되었는지 확인함
// bool isLoggedIn() {
// return (_firebaseAuth.currentUser != null);
// }
Stream<User?> authStateChange() => _firebaseAuth.authStateChanges();
Future<void> createUser(String email, String password) async {
await _firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
//.createUserWithEmailAndPassword(): 유저 이메일/비밀번호로 로그인 인증을 생성함
}
Future<void> userSignOut() async {
await _firebaseAuth.signOut();
//.signOut() 로그아웃하는 메서드
}
Future<void> userSignIn(String email, String password) async {
await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
//.signInWithEmailAndPassword: firebase 이메일,비밀번호로 로그인
}
}
final authRepoProvider = Provider((ref) => AuthRepository());
//AuthRepository() 를 expose 하기 위해 만들어진 Provider
final authStreamProvider =
StreamProvider((ref) => ref.read(authRepoProvider).authStateChange());
//AuthRepository()의 _firebaseAuth.authStateChanges() 로
//sign-in 이나 sign-out의 실시간 변화를 expose하기 위해 만들어진 Provider
3. LoginViewModel 클래스:
- **AsyncNotifier<void>**를 확장한 클래스로, 사용자 로그인 및 관련 작업을 처리합니다.
- logIn, logOut, createUsers, getEmail 메서드: 로그인, 로그아웃, 사용자 생성 및 이메일 가져오기 기능을 제공합니다.
LoginViewModel (ViewModel)
class LoginViewModel extends AsyncNotifier<void> {
late final AuthRepository _authRepository;
@override
FutureOr<void> build() async {
_authRepository = ref.read(authRepoProvider);
//authRepoProvider로 AuthRepository()가 가진 것들을 _authRepository에 넣음
}
Future<void> logIn(
String email, String password, BuildContext context) async {
state = const AsyncValue.loading();
//로딩 상태로 들어감
state = await AsyncValue.guard(
//guard: 에러발생과 데이터 처리결과 상태를 상황에 맞게 state에 넣어줌
() async => await _authRepository.userSignIn(email, password));
if (state.hasError) {
final snackBar = SnackBar(
content: Text(
(state.error as FirebaseException).message ?? "알수없는 오류입니다."));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else {
return;
}
}
Future<void> logOut() async {
await ref.read(authRepoProvider).userSignOut();
}
Future<void> createUsers(String email, String password) async {
state = const AsyncValue.loading();
await ref.read(authRepoProvider).createUser(email, password);
state = const AsyncValue.data(null);
}
String getEmail() {
String email = _authRepository.user?.email ?? "로그인 해주세요.";
return email;
}
}
final userStateProvider = StateProvider((ref) => {});
final userProvider = AsyncNotifierProvider<LoginViewModel, void>(
() => LoginViewModel(),
);
4. LoginPage 클래스:
- **ConsumerStatefulWidget**를 상속한 클래스로, 상태를 사용하여 UI를 업데이트합니다.
- _onTapLogin, _onTapLogout, _onTapCreateUser 메서드: 버튼 클릭 시 수행되는 로그인, 로그아웃, 사용자 생성 동작을 처리합니다.
- build 메서드: UI를 구성하고, 사용자가 로그인되어 있는지 여부에 따라 다른 아이콘 및 텍스트를 표시합니다.
5. 위젯 및 UI:
- **TextFormField**를 사용하여 이메일 및 비밀번호를 입력 받습니다.
- **CupertinoButton**을 사용하여 로그인, 로그아웃, 사용자 생성 기능을 수행하는 버튼을 생성합니다.
- 사용자가 로그인되어 있으면 초록색 아이콘을, 그렇지 않으면 기본 사용자 아이콘을 표시합니다.
LoginPage (View)
class LoginPage extends ConsumerStatefulWidget {
const LoginPage({super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _LoginPageState();
}
class _LoginPageState extends ConsumerState<LoginPage> {
final TextEditingController _emailTextEditingController =
TextEditingController();
final TextEditingController _passwordTextEditingController =
TextEditingController();
Future<void> _onTapLogin() async {
if (_emailTextEditingController.text.isEmpty ||
_passwordTextEditingController.text.isEmpty) return;
ref.read(userProvider.notifier).logIn(_emailTextEditingController.text,
_passwordTextEditingController.text, context);
setState(() {});
}
void _onTapLogout() {
ref.read(userProvider.notifier).logOut();
setState(() {});
}
void _onTapCreateUser() {
ref.read(userProvider.notifier).createUsers(
_emailTextEditingController.text, _passwordTextEditingController.text);
setState(() {});
}
@override
void initState() {
_emailTextEditingController.addListener(() {});
_passwordTextEditingController.addListener(() {});
super.initState();
}
@override
void dispose() {
_emailTextEditingController.dispose();
_passwordTextEditingController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ref.watch(authRepoProvider).isLoggedIn
? Icon(
Icons.gpp_good_sharp,
size: 100,
color: Colors.green.shade200,
)
: const Icon(
Icons.supervised_user_circle,
color: Colors.white,
size: 100,
),
ref.watch(userProvider).isLoading
? const CircularProgressIndicator()
: Text(
ref.read(userProvider.notifier).getEmail(),
style: TextStyle(
color: Colors.amber.shade600,
fontSize: 30,
fontWeight: FontWeight.bold),
),
TextFormField(
textAlign: TextAlign.center,
controller: _emailTextEditingController,
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value != null || value != "") {
return "check Your Email";
} else {
return null;
}
},
),
TextFormField(
textAlign: TextAlign.center,
controller: _passwordTextEditingController,
keyboardType: TextInputType.number,
validator: (value) {
if (value != null || value != "") {
return "It is Not Safe";
} else {
return null;
}
},
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: CupertinoButton(
disabledColor: Colors.grey,
color: Colors.green.shade500,
onPressed: _onTapLogin,
child: const Text("로그인"),
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: CupertinoButton(
disabledColor: Colors.grey,
color: Colors.red.shade500,
onPressed: _onTapLogout,
child: const Text("로그아웃"),
),
),
],
),
Padding(
padding: const EdgeInsets.all(8.0),
child: CupertinoButton(
disabledColor: Colors.grey,
color: Colors.blue.shade300,
onPressed: _onTapCreateUser,
child: const Text("회원으로 가입하기"),
),
),
],
),
)));
}
}
전체소스.
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
////Firebase///////////////////////////////////////////////////////////////////////
class AuthRepository {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
//Firebase의 Authentication을 다룰 수 있도록 FirebaseAuth 인스턴스를 생성함
User? get user => _firebaseAuth.currentUser; //get 정의하는 방법 중 하나
bool get isLoggedIn => user != null;
//_firebaseAuth.currentUser: Firebase에 currentUser가 있는지? 유무로 로그인 되었는지 확인함
// bool isLoggedIn() {
// return (_firebaseAuth.currentUser != null);
// }
Stream<User?> authStateChange() => _firebaseAuth.authStateChanges();
Future<void> createUser(String email, String password) async {
await _firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
//.createUserWithEmailAndPassword(): 유저 이메일/비밀번호로 로그인 인증을 생성함
}
Future<void> userSignOut() async {
await _firebaseAuth.signOut();
//.signOut() 로그아웃하는 메서드
}
Future<void> userSignIn(String email, String password) async {
await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
//.signInWithEmailAndPassword: firebase 이메일,비밀번호로 로그인
}
}
final authRepoProvider = Provider((ref) => AuthRepository());
//AuthRepository() 를 expose 하기 위해 만들어진 Provider
final authStreamProvider =
StreamProvider((ref) => ref.read(authRepoProvider).authStateChange());
//AuthRepository()의 _firebaseAuth.authStateChanges() 로
//sign-in 이나 sign-out의 실시간 변화를 expose하기 위해 만들어진 Provider
////ViewModel/////////////////////////////////////////////////////////////////////////
class LoginViewModel extends AsyncNotifier<void> {
late final AuthRepository _authRepository;
@override
FutureOr<void> build() async {
_authRepository = ref.read(authRepoProvider);
//authRepoProvider로 AuthRepository()가 가진 것들을 _authRepository에 넣음
}
Future<void> logIn(
String email, String password, BuildContext context) async {
state = const AsyncValue.loading();
//로딩 상태로 들어감
state = await AsyncValue.guard(
//guard: 에러발생과 데이터 처리결과 상태를 상황에 맞게 state에 넣어줌
() async => await _authRepository.userSignIn(email, password));
if (state.hasError) {
final snackBar = SnackBar(
content: Text(
(state.error as FirebaseException).message ?? "알수없는 오류입니다."));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else {
return;
}
}
Future<void> logOut() async {
await ref.read(authRepoProvider).userSignOut();
}
Future<void> createUsers(String email, String password) async {
state = const AsyncValue.loading();
await ref.read(authRepoProvider).createUser(email, password);
state = const AsyncValue.data(null);
}
String getEmail() {
String email = _authRepository.user?.email ?? "로그인 해주세요.";
return email;
}
}
final userStateProvider = StateProvider((ref) => {});
final userProvider = AsyncNotifierProvider<LoginViewModel, void>(
() => LoginViewModel(),
);
////View////////////////////////////////////////////////////////////////////
class LoginPage extends ConsumerStatefulWidget {
const LoginPage({super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _LoginPageState();
}
class _LoginPageState extends ConsumerState<LoginPage> {
final TextEditingController _emailTextEditingController =
TextEditingController();
final TextEditingController _passwordTextEditingController =
TextEditingController();
Future<void> _onTapLogin() async {
if (_emailTextEditingController.text.isEmpty ||
_passwordTextEditingController.text.isEmpty) return;
ref.read(userProvider.notifier).logIn(_emailTextEditingController.text,
_passwordTextEditingController.text, context);
setState(() {});
}
void _onTapLogout() {
ref.read(userProvider.notifier).logOut();
setState(() {});
}
void _onTapCreateUser() {
ref.read(userProvider.notifier).createUsers(
_emailTextEditingController.text, _passwordTextEditingController.text);
setState(() {});
}
@override
void initState() {
_emailTextEditingController.addListener(() {});
_passwordTextEditingController.addListener(() {});
super.initState();
}
@override
void dispose() {
_emailTextEditingController.dispose();
_passwordTextEditingController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ref.watch(authRepoProvider).isLoggedIn
? Icon(
Icons.gpp_good_sharp,
size: 100,
color: Colors.green.shade200,
)
: const Icon(
Icons.supervised_user_circle,
color: Colors.white,
size: 100,
),
ref.watch(userProvider).isLoading
? const CircularProgressIndicator()
: Text(
ref.read(userProvider.notifier).getEmail(),
style: TextStyle(
color: Colors.amber.shade600,
fontSize: 30,
fontWeight: FontWeight.bold),
),
TextFormField(
textAlign: TextAlign.center,
controller: _emailTextEditingController,
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value != null || value != "") {
return "check Your Email";
} else {
return null;
}
},
),
TextFormField(
textAlign: TextAlign.center,
controller: _passwordTextEditingController,
keyboardType: TextInputType.number,
validator: (value) {
if (value != null || value != "") {
return "It is Not Safe";
} else {
return null;
}
},
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: CupertinoButton(
disabledColor: Colors.grey,
color: Colors.green.shade500,
onPressed: _onTapLogin,
child: const Text("로그인"),
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: CupertinoButton(
disabledColor: Colors.grey,
color: Colors.red.shade500,
onPressed: _onTapLogout,
child: const Text("로그아웃"),
),
),
],
),
Padding(
padding: const EdgeInsets.all(8.0),
child: CupertinoButton(
disabledColor: Colors.grey,
color: Colors.blue.shade300,
onPressed: _onTapCreateUser,
child: const Text("회원으로 가입하기"),
),
),
],
),
)));
}
}
반응형
'앱 개발 > Flutter' 카테고리의 다른 글
51. [Flutter] FireStore 연동하기(NoSQL DB) (0) | 2023.12.19 |
---|---|
50. [Flutter] Firebase Authentication (깃허브) 인증 구현하기 (1) | 2023.12.07 |
48. [Flutter] Riverpod Provider 종류와 사용법(with MVVM 패턴) (1) | 2023.11.24 |
47. [Flutter] MVVM + Repository 적용, 상태관리(with Riverpod, SharedPreferences 패키지) (0) | 2023.11.17 |
46. [Flutter] Provider 상태관리 (0) | 2023.11.15 |
Comments