


nvm ์ํnvm install 20.11.0 ์
๋ ฅ ํ node-vโ npm -v๋ก ํ์ธnpm install -g firebase-tools๊ด๋ฆฌ์๋ก ์คํํด์ ๋ณ๊ฒฝ ๋์ - Set-ExecutionPolicy RemoteSignedโ [A] ๋ชจ๋ ์(A) ์
๋ ฅfirebase login:list
firebase projects:list

dart pub global activate flutterfire_cli
C:/Users/User/AppData/Local/Pub/Cache/binexport PATH="$PATH:C:/Users/User/AppData/Local/Pub/Cache/bin"
alias flutterfire=flutterfire.bat(PowerShell ํ๊ฒฝ)$env:Path += ";$env:LOCALAPPDATA\Pub\Cache\bin"flutterfire --version
flutterfire configure --project=[projectname]


1๏ธโฃ ํ์ํ package ์ถ๊ฐ - firebase_core: ^4.13.0
2๏ธโฃ main.dart์ package ์ถ๊ฐ
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
. . .//code ์ค์ ํ์
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
3๏ธโฃ ์ฝ์๋ก ์ด๋ํ๊ธฐ

minSdk = flutter.targetSdkVersionโ minSdk = 23์ผ๋ก ๋ฐ๊ฟ์ฃผ์ธ์.

#filename:pubspec.yaml
firebase_auth: ^6.5.7

// filename: login_page.dart
//... code ์ค์ํ์
class LoginPage extends StatelessWidget {
LoginPage({super.key});
Widget build(BuildContext context) {
TextTheme textTheme = Theme.of(context).textTheme;
ColorScheme colorScheme = Theme.of(context).colorScheme;
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'My perfect workout mate',
style: textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
color: colorScheme.primary,
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'Workout\nTracker',
style: textTheme.displayLarge?.copyWith(
fontWeight: FontWeight.w700,
color: colorScheme.primary,
),
),
Padding(
padding: EdgeInsets.only(bottom: 10),
child: Image.asset(
'assets/runner_icon.png',
width: 32,
color: colorScheme.primary,
),
),
],
),
],
),
),
),
);
}
}
TextField์ TextFormField ์ฌ์ฉTextFormField ์ฌ์ฉTextFormField ์ฌ์ฉ ๋ฐฉ๋ฒForm ์์ ฏ๊ณผ ํจ๊ป ์ฌ์ฉForm์ key๋ฅผ ๋ฑ๋กํ์ฌ ๋ด๋ถ TextFormField์ ์ํ ์ถ์ key๋ฅผ ํตํ ์
๋ ฅ๊ฐ ์ ์ฅ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌemail, password ๋ณ์ ์ ์Form ๋ด๋ถ ์
๋ ฅ๊ฐ ์ ์ฅ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํ _formKey ์ ์obscureText: true ํ๋กํผํฐ : ๋น๋ฐ๋ฒํธ ์
๋ ฅ์ ํ๋ฉด์ *๋ก ๋ณด์ด๋๋ก ํด์ฃผ๋ ๊ธฐ๋ฅvalidator, onSaved ํ๋กํผํฐ ํ์ฉํด์ ์ ํจ์ฑ ๊ฒ์ฌ ๊ด๋ฆฌ _formKey.currentState?.validate()๋ฅผ ํธ์ถํ์ฌ ๊ฐ TextFormField์ validator ์คํ_formKey.currentState?.save()๋ฅผ ํธ์ถํ์ฌ ๋ชจ๋ TextFormField์ onSaved ์คํonSaved๋ฅผ ํตํด ์ฌ์ฉ์ ์
๋ ฅ๊ฐ์ email, password ๋ณ์์ ํ ๋นcurrentState
GlobalKey์ ์ฐ๊ฒฐ๋ ์์ ฏ์ ํ์ฌState๊ฐ์ฒด- ํด๋น ์์ ฏ์ ์ค์ ์ํ์ ๋ฉ์๋์ ์ ๊ทผํ๊ธฐ ์ํด ์ฌ์ฉ
FormState์validate(),save(),reset()๋ฑ์ ๋ฉ์๋ ํธ์ถ ๊ฐ๋ฅ
//filename: login_page.dart
//...code ์ค์ํ์
class LoginPage extends StatelessWidget {
LoginPage({super.key});
final _formKey = GlobalKey<FormState>();
String? email;
String? password;
Widget build(BuildContext context) {
. . .//code ์ค์ํ์
Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'My perfect workout mate',
style: textTheme.titleMedium?.copyWith(. . .),
),
Row(. . .),
SizedBox(height: 50),
TextFormField(
style: TextStyle(color: Colors.black),
decoration: InputDecoration(
labelText: 'Email',
labelStyle: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
),
filled: true,
fillColor: Colors.grey.shade200,
hintText: 'example@example.com',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
prefixIcon: Icon(Icons.email_outlined),
),
),
SizedBox(height: 20),
TextFormField(
style: TextStyle(color: Colors.black),
decoration: InputDecoration(
labelText: 'Password',
labelStyle: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
),
filled: true,
fillColor: Colors.grey.shade200,
prefixIcon: Icon(Icons.lock_outline, color: Colors.black),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
),
obscureText: true,
validator: (value){
if(value==null || value.isEmpty){
return 'ํจ์ค์๋๋ฅผ ์
๋ ฅํ์ธ์';
}
if(value.length < 6){
return '6์๋ฆฌ ์ด์ ์
๋ ฅํ์ธ์';
} // firebase๊ฐ 6์๋ฆฌ ์ดํ๋ ์ง์ํ์ง ์์
return null;
},
onSaved: (value){
password=value;
},
),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
if(_formKey.currentState?.validate() ?? false){
_formKey.currentState?.save();
}
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 15),
backgroundColor: colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: Text(
'๋ก๊ทธ์ธ',
style: textTheme.titleLarge?.copyWith(
color: colorScheme.onPrimary,
),
),
),
),
],
),
),
),
);
}
}
FittedBoxText ์์ ฏ์ ํฌ๊ธฐ ์๋ ์กฐ์ Flexibleoverflow ๋ฐฉ์ง//filename: login_page.dart
//. . .code ์ค์ํ์
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
'My perfect workout mate',
style: textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
color: colorScheme.primary,
),
),
),
),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'Workout\nTracker',
style: textTheme.displayLarge?.copyWith(
fontWeight: FontWeight.w700,
color: colorScheme.primary,
),
),
Padding(
padding: EdgeInsets.only(bottom: 10),
child: Image.asset(
'assets/runner_icon.png',
width: 32,
color: colorScheme.primary,
),
),
],
),
),
),
//. . .code ์ค์ํ์
registration_page.dart ํ์ผ์ RegistrationPage StatelessWidget ์์ฑmy_router.dart์ ํ์๊ฐ์
ํ์ด์ง ์ถ๊ฐcontext.goํ์ฉํด์ login_page.dart์์ Sign up ๋ฒํผ ํด๋ฆญํ๋ฉด RegistrationPage๋ก ์ด๋ํ๊ฒ ์ค์ final TextEditingController _passwordController = TextEditingController(); helperText๋ฅผ ํตํด ๋น๋ฐ๋ฒํธ ๊ท์น์ ์ค๋ช
// filename: registration_page.dart
// . . .code ์ค์ํ์
class RegistrationPage extends StatelessWidget {
RegistrationPage({super.key});
String? name;
String? email;
String? password;
final TextEditingController _passwordController = TextEditingController();
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ํ์๊ฐ์
'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(. . . ),
TextFormField(. . .),//์ด๋ฆ field
TextFormField(. . .),//์ด๋ฉ์ผ field
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: '๋น๋ฐ๋ฒํธ',
labelStyle: textTheme.headlineSmall,
hintText: '๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํ์ธ์',
helperText: '*๋น๋ฐ๋ฒํธ๋ 6์ ์ด์ ์
๋ ฅํด์ฃผ์ธ์.',
helperStyle: textTheme.bodySmall?.copyWith(
color: colorScheme.primary,
),
border: UnderlineInputBorder(),
floatingLabelBehavior: FloatingLabelBehavior.always,
),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return '๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํ์ธ์';
}
if (value.length < 6) {
return '๋น๋ฐ๋ฒํธ๋ 6์ ์ด์์ด์ด์ผ ํฉ๋๋ค';
}
return null;
},
onSaved: (value) {
password = value;
},
),
],
),
),
}
}
signUpWithEmail : ํ์๊ฐ์
signInWithEmail : ๋ก๊ทธ์ธsignOut : ๋ก๊ทธ์์resetPassword : ๋น๋ฐ๋ฒํธ ์ฌ์ค์ Profile : ์์ deleteAccount : ํํดfirebase_auth: ^6.5.7firebase_auth_service.dart ํ์ผ์ FirebaseAuthService class ์ ์_auth.setLanguageCode('kr');//filename:firebase_aut_service.dart
import 'package:firebase_auth/firebase_auth.dart';
class FirebaseAuthService {
final FirebaseAuth _auth;
FirebaseAuthService() : _auth = FirebaseAuth.instance {
_auth.setLanguageCode('kr');
}
Future<void> signUpWithEmail() async {
//ํ์๊ฐ์
์ฝ๋ ์์ฑ
}
Future<void> signInWithEmail() async {
//๋ก๊ทธ์ธ ์ฝ๋ ์์ฑ
}
Future<void> resetPassword(String email) async {
// ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ์ฝ๋ ์์ฑ
}
Future<void> signOut() async {
//๋ก๊ทธ์์ ์ฝ๋ ์์ฑ
}
Future<void> deleteAccount() async {
//๊ณ์ ์ญ์ ์ฝ๋ ์์ฑ
}
}
createUserWithEmailAndPassword method: ํ์๊ฐ์
์ฒ๋ฆฌ updateDisplayName() method: ์ด๋ฆ ์ ์ฅ sendEmailVerification method: ์ด๋ฉ์ผ verification(์๋์ผ๋ก ์ด๋ฉ์ผ ๋ฐ์ก) //filename:firebase_auth_service.dart
//. . .code ์ค์ํ์
Future<void> signUpWithEmail({
required String email,
required String password,
String? name,
}) async {
await _auth.createUserWithEmailAndPassword(email: email, password: password);
await _auth.currentUser?.updateDisplayName(name);
await _auth.currentUser?.sendEmailVerification();
}
email-already-in-useweak-password//filename:firebase_aut_service.dart
//. . .code ์ค์ํ์
Future<void> signUpWithEmail({
required String email,
required String password,
String? name,
}) async {
String? errorMessage;
try {
await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
await _auth.currentUser?.updateDisplayName(name);
await _auth.currentUser?.sendEmailVerification();
} on FirebaseAuthException catch (error) {
switch (error.code) {
case 'weak-password':
errorMessage = '์ทจ์ฝํ ํจ์ค์๋์
๋๋ค. ์ต์ 6์๋ฆฌ ์ด์์ ๋ฌธ์๋ฅผ ์
๋ ฅํ์ธ์.';
break;
case 'email-already-in-use':
errorMessage = '์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ์
๋๋ค. ๋ค๋ฅธ ์ด๋ฉ์ผ์ ์
๋ ฅํ์ธ์.';
break;
case 'invalid-email':
errorMessage = '์ ํจํ์ง ์์ ์ด๋ฉ์ผ์
๋๋ค.';
break;
default:
errorMessage = error.message ?? '์ ์ ์๋ ์๋ฌ ๋ฐ์';
}
} catch (error) {
errorMessage = '์ ์ ์๋ ์๋ฌ ๋ฐ์';
}
if (errorMessage != null) {
throw Exception(errorMessage);
}
}
show_snackbar.dart ์์ฑํด์ ํจ์๋ก refactoring // filename: show_snackbar.dart
import 'package:flutter/material.dart';
void showSnackBar(BuildContext context, String text) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(text),
),
);
}
// filename: registration_page.dart
import 'show_snackbar.dart';
. . .//code์ค์ํ์
class RegistrationPage extends StatelessWidget {
. . .//code์ค์ํ์
final _auth = FirebaseAuthService();
// ...code ์ค์ ํ์
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
_auth.signUpWithEmail(
email: email!,
password: password!,
name: name!,
)
.then((_) {
showSnackBar(context, 'ํ์๊ฐ์
์ด ์๋ฃ๋์์ต๋๋ค.');
context.go('/workout_home');
})
.catchError((error) {
showSnackBar(context, error.toString());
});
}
},
),
// ...code ์ค์ ํ์
firebase authentication web consoleโ Authenticationโ UsersignInWithEmail ๋ฉ์๋ ์์ฑsignInWithEmailAndPassword() method๋ก login์ ์ฐจ ์งํawait _auth.signInWithEmailAndPassword(email: email, password: password);