Flutter 스터디 26 Firebase 연동 및 SignUp/SignIn 기능 구현
조금 매운맛 23 파이어베이스 연동하기
1. 파이어베이스 홈페이지
Firebase
Firebase는 고품질 앱을 빠르게 개발하고 비즈니스를 성장시키는 데 도움이 되는 Google의 모바일 플랫폼입니다.
firebase.google.com
2. 콘솔로 이동
3. 프로젝트 만들기
4. 프로젝트명
5. 그냥 두고 다음
6. 대한민국 선택
7. 웹콘솔에서 안드로이드앱
8. 앱등록
(* 안드로이드 스튜디오)
9. 파일 다운로드 후 안드로이드 앱 폴더에 넣기
(*안드스튜디오)
10. firebase SDK 설치
(* 안드스튜디오)
> (안드로이드-앱-빌드그레이들아님!!!!!!)
11. 설정 끝
12. 플러터 파이어베이스 패키지 설치 : flutter firebase firestore
https://pub.dev/packages/cloud_firestore
> 알아서 설치
이러고 에뮬레이터 실행하면 에러뜸 FAILURE: Build failed with an exception. 샤갈
플러터 패키지의 문제
1) android > app > build.gradle 이동
2) 아래 코드 defaultCofig 내 추가
multiDexEnabled true
3) dependencies 내 추가
implementation 'com.android.support:multidex:2.0.1'
4) android > app > build.gradle 내 plugin 수정
apply plugin: 'com.google.gms.google-services'
> 아무생각없이 id 'com.google.gms.google-services' 붙여넣어서 오류가 떳다.
+ 1년 반 전 강의보고 공부 중인데 아직까지 수정이 되지 않았네? 하하
그러면 또 에러가 이렇게 뜨는데
5) android > app > build.gradle 내 minSdkVersion 19 수정
minSdkVersion 19
그럼 진짜 끝~
조금 매운맛 24 파이어베이스 사인업-사인인 기능 구현
1. firebase core 설치 - 파이어베이스 관련 작업 시 필요 패키지
https://pub.dev/packages/firebase_core
- 위의 패키지를 통해서 Firebase.initializeApp() 메서드 호출 가능
: 플러터에서 파이어 베이스를 사용하기 전에 무조건 호출해야함. 안하면 에러뜸
- main.dart에 import
import 'package:firebase_core/firebase_core.dart';
- void main()에 추가
WidgetsFlutterBinding.ensureInitialized();
> 플러터에서 파이어베이스 사용시 초기화 메서드 Firebase.initializeApp()는 비동기 방식
> 초기화 메서드인 Firebase.initializeApp()은 플러터와 통신 하기를 원하지만, runApp 메소드가 호출되기 전에는
플러터 엔진이 초기화 되지 않아서 접근이 불가능함
> 따라서 main 메소드 내부에서 플러터엔진과 관련된 비동기 메소드(예)Firebase.initializeApp())를 사용하려면 먼저 플러터 코어 엔진을 초기화 시켜줘야함
> 이를 실행하는게 WidgetsFlutterBinding.ensureInitialized(); 메소드
2. firebase auth 설치 - 로그인 인증 관련 패키지
https://pub.dev/packages/firebase_auth
3. TextFormField의 onChanged 메서드를 이용해 TextFormField에 입력하는 값을 저장해서 가져오기
(기능면에선 onSaved와 유사)
onChanged: (value){
userName = value;
},
> email, password에도 똑같이
실제로 버튼을 누르면 값이 저장되는지 확인
4. 전송버튼 positoined의 onTap에 프린트함수 추가
onTap: (){
_tryValidation();
print(userName);
print(userEmail);
print(userPassword);
},
* 패스워드 가리기
obscureText: true,
* 이메일 입력시 이메일 입력 키보드
keyboardType: TextInputType.emailAddress,
4. 사용자 등록기능 구현
//main_screen.dart
import 'package:firebase_auth/firebase_auth.dart';
class _LoginSignupScreenState extends State<LoginSignupScreen> {
final _authentication = FirebaseAuth.instance;
> 한번 생성되면 변하지 않기 때문에 final
> private 위해서 _(언더스코어)
전송 버튼 하나로 signUp과 signIn 두가지를 실행해야함
if(isSignupScreen){ //사인업 스크린일 경우
_tryValidation();
_authentication.createUserWithEmailAndPassword(
email: userEmail,
password: userPassword,
);
}
_authentication.createUserWithEmailAndPassword 메서드
: future<UserCrential> 타입, email과 password를 required argument로 가짐
: Future를 반환함 > 사용자 등록이 끝난후 다음 과정 진행 되야함 > async 방식으로 변환 / await 키워드
- SignUp 시 여러가지 경우로 실패할수 있으니 try ~ catch 문으로 정리
//main_screen.dart
child: GestureDetector(
onTap: () async {
if (isSignupScreen) {
_tryValidation();
try {
final newUser = await _authentication
.createUserWithEmailAndPassword(
email: userEmail, password: userPassword);
if (newUser.user != null) { // 회원 등록 성공
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatScreen())); // 바로 채팅화면으로 넘어가기
}
} catch (e) {
print(e);
//사용자에게 시각적으로 에러 표시
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content:
Text('Please check your email and password'),
backgroundColor: Colors.blue,
));
}
}
},
screens > chat_screen.dart 생성
//chat_screen.dart
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
class ChatScreen extends StatefulWidget {
const ChatScreen({Key? key}) : super(key: key);
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _authentication = FirebaseAuth.instance;
User? loggedUser; //회원가입을 해서 정보가 들어올때 초기화되므로 널세이프티
@override
void initState() { //초기화 될때 getCurrentUser() 진행
// TODO: implement initState
super.initState();
getCurrentUser();
}
void getCurrentUser() { //유저 정보 확인
try {
final user = _authentication.currentUser;
if (user != null) {
loggedUser = user;
print(loggedUser!.email);
}
} catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Chat screen'),
),
body: Center(
child: Text('Chat screen'),
));
}
}
5. 로그인 기능
회원가입 if(isSignupScreen) 밑에 로그인 if(!isSignupScreen) 을 구현
//main_screen.dart
if (!isSignupScreen) {
try {
_tryValidation();
final newUser =
await _authentication.signInWithEmailAndPassword(
email: userEmail, password: userPassword);
if (newUser.user != null) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatScreen()));
}
} catch (e) {
print(e);
}
}
6. 로그아웃기능
//chat_screen.dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Chat screen'),
actions: [
IconButton(onPressed: () {
_authentication.signOut(); //로그아웃 기능
},
icon: Icon(Icons.exit_to_app_sharp)
)
],
),
body: Center(
child: Text('Chat screen'),
));
}
}
7. 파이어베이스 연동마무리
1) 파이어베이스 > 콘솔 > 프로젝트
2) 시작 > sign-in method > 이메일 > 활성화
* 주의 : 파이어베이스 연동 시 비밀번호는 반드시 6글자 이상! 아니면 에러
- 회원가입 실행 결과
- 로그인 / 로그아웃 생략
//main.dart
import 'package:flutter/material.dart';
import 'package:yami_chat/screens/main_screen.dart';
import 'package:firebase_core/firebase_core.dart';
void main() async{
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
backgroundColor: Colors.white,
),
home: LoginSignupScreen(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
//main_screen.dart
import 'package:flutter/material.dart';
import 'package:yami_chat/config/palette.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:yami_chat/screens/chat_screen.dart';
class LoginSignupScreen extends StatefulWidget {
const LoginSignupScreen({Key? key}) : super(key: key);
@override
State<LoginSignupScreen> createState() => _LoginSignupScreenState();
}
class _LoginSignupScreenState extends State<LoginSignupScreen> {
final _authentication = FirebaseAuth.instance;
bool isSignupScreen = true;// 로그인가입화면 state 관리를 위해서
final _formKey = GlobalKey<FormState>();
String userName = '';
String userEmail = '';
String userPassword = '';
void _tryValidation(){
final isValid = _formKey.currentState!.validate();
if(isValid){
_formKey.currentState!.save();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Palette.backgroundColor,
body: GestureDetector(
onTap: (){
FocusScope.of(context).unfocus();
},
child: Stack(
children: [
//배경
Positioned(
top: 0,
left: 0, // 스크린 상단 시작점 맞추기위해서 좌우 0
right: 0,
child: Container(
height: 300,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('image/red.jpg'),
fit: BoxFit.fill //스크린 상단 끝까지 채우기
),
),
child: Container(
padding: EdgeInsets.only(top: 90, left: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(text: TextSpan( //문단등 구성할 수 있게 해주는 위젯
text: 'Welcome',
style: TextStyle(
letterSpacing: 1.0,
fontSize: 25,
color: Colors.white,
),
children: [
TextSpan( //문단등 구성할 수 있게 해주는 위젯
text: isSignupScreen ? ' To Yummy chat!' : ' back',
style: TextStyle(
letterSpacing: 1.0,
fontSize: 25,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
],
),
),
SizedBox(
height: 5.0,
),
Text(
isSignupScreen ? 'Signup to continue' : 'Signin to continue',
style: TextStyle(
letterSpacing: 1.0,
color: Colors.white,
),
),
],
),
),
),),
//텍스트폼필드
AnimatedPositioned(//
duration: Duration(milliseconds: 500),
curve: Curves.easeIn,
top: 180.0,
child:
Container(
padding: EdgeInsets.all(20.0),
height: isSignupScreen ? 280.0 : 200.0,
width: MediaQuery.of(context).size.width-40, // MediaQuery 이용해서 디바이스의 크기에 맞춰 -40
margin: EdgeInsets.symmetric(horizontal: 20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 15,
spreadRadius: 5,
)
]
),
child: SingleChildScrollView(
padding: EdgeInsets.only(bottom: 20),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
GestureDetector(
onTap: () {
setState(() {
isSignupScreen = false;
});
},
child: Column(
children: [
Text(
'LOGIN',
style: TextStyle(
fontSize: 16,
color: !isSignupScreen
? Palette.activeColor
: Palette.textColor1,
fontWeight: FontWeight.bold),
),
if (!isSignupScreen)
Container(
height: 2,
width: 55,
color: Colors.orange,
)
],
),
),
GestureDetector(
onTap: () {
setState(() {
isSignupScreen = true;
});
},
child: Column(
children: [
Text(
'SIGNUP',
style: TextStyle(
fontSize: 16,
color: isSignupScreen
? Palette.activeColor
: Palette.textColor1,
fontWeight: FontWeight.bold),
),
if (isSignupScreen)
Container(
margin: EdgeInsets.only(top: 3),
height: 2,
width: 55,
color: Colors.orange,
)
],
),
)
],
),
if(isSignupScreen)
Container(
margin: EdgeInsets.only(top: 20),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
key: ValueKey(1),
validator: (value){
if(value!.isEmpty || value.length < 4) {
return 'Please enter at least 4 characters';
} return null;
},
onSaved: (value){
userName = value!;
},
onChanged: (value){
userName = value;
},
decoration: InputDecoration(
prefixIcon: Icon(Icons.account_circle,
color: Palette.iconColor,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Palette.textColor1
),
borderRadius: BorderRadius.all(Radius.circular(35.0),
),
),
focusedBorder: OutlineInputBorder( // textfield 선택 했을 때 보더라인 보여주는 기능
borderSide: BorderSide(
color: Palette.textColor1
),
borderRadius: BorderRadius.all(Radius.circular(35.0),
),
),
hintText: 'User name',
hintStyle: TextStyle(
fontSize: 14,
color: Palette.textColor1
),
contentPadding: EdgeInsets.all(10)
),
),
SizedBox(
height: 8,
),
TextFormField(
keyboardType: TextInputType.emailAddress,
key: ValueKey(2),
validator: (value){
if(value!.isEmpty || value.contains('@')) {
return 'Please enter a valid email address';
} return null;
},
onSaved: (value){
userEmail = value!;
},
onChanged: (value){
userEmail = value;
},
decoration: InputDecoration(
prefixIcon: Icon(Icons.email_sharp,
color: Palette.iconColor,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Palette.textColor1
),
borderRadius: BorderRadius.all(Radius.circular(35.0),
),
),
focusedBorder: OutlineInputBorder( // textfield 선택 했을 때 보더라인 보여주는 기능
borderSide: BorderSide(
color: Palette.textColor1
),
borderRadius: BorderRadius.all(Radius.circular(35.0),
),
),
hintText: 'email',
hintStyle: TextStyle(
fontSize: 14,
color: Palette.textColor1
),
contentPadding: EdgeInsets.all(10)
),
),
SizedBox(
height: 8,
),
TextFormField(
obscureText: true,
key: ValueKey(3),
validator: (value){
if(value!.isEmpty || value.length < 6) {
return 'Password must be at least 7 characters long';
} return null;
},
onSaved: (value){
userPassword = value!;
},
onChanged: (value){
userPassword = value;
},
decoration: InputDecoration(
prefixIcon: Icon(Icons.lock,
color: Palette.iconColor,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Palette.textColor1
),
borderRadius: BorderRadius.all(Radius.circular(35.0),
),
),
focusedBorder: OutlineInputBorder( // textfield 선택 했을 때 보더라인 보여주는 기능
borderSide: BorderSide(
color: Palette.textColor1
),
borderRadius: BorderRadius.all(Radius.circular(35.0),
),
),
hintText: 'password',
hintStyle: TextStyle(
fontSize: 14,
color: Palette.textColor1
),
contentPadding: EdgeInsets.all(10)
),
)
],
),
),
),
if(!isSignupScreen)
Container(
margin: EdgeInsets.only(top: 20),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
key: ValueKey(4),
validator: (value){
if(value!.isEmpty || value.contains('@')) {
return 'Please enter a valid email address';
} return null;
},
onSaved: (value){
userEmail = value!;
},
onChanged: (value){
userEmail = value;
},
decoration: InputDecoration(
prefixIcon: Icon(Icons.account_circle,
color: Palette.iconColor,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Palette.textColor1
),
borderRadius: BorderRadius.all(Radius.circular(35.0),
),
),
focusedBorder: OutlineInputBorder( // textfield 선택 했을 때 보더라인 보여주는 기능
borderSide: BorderSide(
color: Palette.textColor1
),
borderRadius: BorderRadius.all(Radius.circular(35.0),
),
),
hintText: 'User name',
hintStyle: TextStyle(
fontSize: 14,
color: Palette.textColor1
),
contentPadding: EdgeInsets.all(10)
),
),
SizedBox(
height: 8.0,
),
TextFormField(
obscureText: true,
key: ValueKey(5),
validator: (value){
if(value!.isEmpty || value.length < 6) {
return 'Password must be at least 7 characters long';
} return null;
},
onSaved: (value){
userPassword = value!;
},
onChanged: (value){
userPassword = value;
},
decoration: InputDecoration(
prefixIcon: Icon(Icons.lock,
color: Palette.iconColor,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Palette.textColor1
),
borderRadius: BorderRadius.all(Radius.circular(35.0),
),
),
focusedBorder: OutlineInputBorder( // textfield 선택 했을 때 보더라인 보여주는 기능
borderSide: BorderSide(
color: Palette.textColor1
),
borderRadius: BorderRadius.all(Radius.circular(35.0),
),
),
hintText: 'Password',
hintStyle: TextStyle(
fontSize: 14,
color: Palette.textColor1
),
contentPadding: EdgeInsets.all(10)
),
),
],
),
),
)
],
),
),
),
),
//전송버튼
AnimatedPositioned(
duration: Duration(milliseconds: 150),
curve: Curves.easeIn,
top: isSignupScreen ? 430 : 350,
right:0,
left: 0,
child: Center(
child: Container(
padding: EdgeInsets.all(15),
height: 90,
width: 90,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(50),
),
child: GestureDetector(
onTap: () async {
if (isSignupScreen) { //회원가입
_tryValidation();
try {
final newUser = await _authentication
.createUserWithEmailAndPassword(
email: userEmail, password: userPassword);
if (newUser.user != null) { // 회원 등록 성공
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatScreen())); // 바로 채팅화면으로 넘어가기
}
} catch (e) {
print(e);
//사용자에게 시각적으로 에러 표시
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content:
Text('Please check your email and password'),
backgroundColor: Colors.blue,
));
}
}
if (!isSignupScreen) { //로그인
try {
_tryValidation();
final newUser =
await _authentication.signInWithEmailAndPassword(
email: userEmail, password: userPassword);
if (newUser.user != null) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatScreen()));
}
} catch (e) {
print(e);
}
}
},
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.orange,
Colors.red
],
begin: Alignment.topLeft,
end: Alignment.bottomRight
),
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
spreadRadius: 1,
blurRadius: 1,
offset: Offset(0,1), //버튼으로부터 수직수평 거리
),
],
),
child: Icon(
Icons.arrow_forward,
color: Colors.white,
),
),
),
),
)),
//signUp과 구글버튼
AnimatedPositioned(
duration: Duration(milliseconds: 200),
top: isSignupScreen ? MediaQuery.of(context).size.height-125 : MediaQuery.of(context).size.height-185,
right: 0,
left: 0,
child: Column(
children: [
Text(isSignupScreen ? 'or Signup with' : 'or Sign with'),
SizedBox(
height: 10,
),
TextButton.icon(
style: TextButton.styleFrom(
primary: Colors.white,
minimumSize: Size(155, 40),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)
),
backgroundColor: Palette.googleColor
),
icon: Icon(Icons.add),
onPressed: (){},
label: Text('Google'),
)
],
)
,),
],
),
),
);
}
}
//chat_screen.dart
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
class ChatScreen extends StatefulWidget {
const ChatScreen({Key? key}) : super(key: key);
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _authentication = FirebaseAuth.instance;
User? loggedUser; //회원가입을 해서 정보가 들어올때 초기화되므로 널세이프티
@override
void initState() { //초기화 될때 getCurrentUser() 진행
// TODO: implement initState
super.initState();
getCurrentUser();
}
void getCurrentUser() { //유저 정보 확인
try {
final user = _authentication.currentUser;
if (user != null) {
loggedUser = user;
print(loggedUser!.email);
}
} catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Chat screen'),
actions: [
IconButton(onPressed: () {
_authentication.signOut();
Navigator.pop(context);
},
icon: Icon(Icons.exit_to_app_sharp)
)
],
),
body: Center(
child: Text('Chat screen'),
));
}
}