Flutter 학습/채팅 앱 만들기
채팅앱 만들기 3 (Auth)
Everyday Growing Engineer
2023. 2. 22. 17:45
1. Firebase 부분 먼저 설정
Firebase project페이지에서 Authentication 클릭.
이메일/비밀번호 클릭
설정후 저장
2. pubspec.yaml 파일에 아랫 부분 추가후 저장
firebase_auth: ^4.2.9
3. main.dart
더보기
import 'package:chat_app/screens/auth_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'screens/chat_screen.dart';
import 'package:firebase_core/firebase_core.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _initialized = false;
bool _error = false;
void initializeFlutterFire() async {
try {
await Firebase.initializeApp();
setState(() {
_initialized = true;
});
} catch (e) {
setState(() {
_error = true;
});
}
}
@override
void initState() {
initializeFlutterFire();
super.initState();
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
if (_error) {
return Center(
child: CircularProgressIndicator(),
);
}
// Show a loader until FlutterFire is initialized
if (!_initialized) {
return Center(
child: CircularProgressIndicator(),
);
}
return MaterialApp(
debugShowCheckedModeBanner: false, // hide "debug" banner
title: 'FlutterChat',
theme: ThemeData(
primarySwatch: Colors.pink,
backgroundColor: Colors.pink,
accentColor: Colors.deepPurple,
accentColorBrightness: Brightness.dark,
buttonTheme: ButtonTheme.of(context).copyWith(
buttonColor: Colors.pink,
textTheme: ButtonTextTheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
),
home: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (ctx, userSnapshot) {
if (userSnapshot.hasData) {
return ChatScreen();
}
return AuthScreen();
}),
);
}
}
- main.dart에서 home 부분 StreamBuilder부분 추가으로 변경
- ThemeData 부분도 수정
- Login 여부에 따라 ChatScreen, AuthScreen 나눠줌.
4. auth_screen.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 '../widgets/auth_form.dart';
class AuthScreen extends StatefulWidget {
const AuthScreen({super.key});
@override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
final _auth = FirebaseAuth.instance;
var _isLoading = false;
// Auth 메소드
void _submitAuthForm(
String email,
String password,
String username,
bool isLogin,
BuildContext ctx,
) async {
UserCredential authResult;
//로그인이나 회원가입 체크하기전에는 로딩중이라는 표시
setState(() {
_isLoading = true;
});
try {
if (isLogin) {
authResult = await _auth.signInWithEmailAndPassword(
email: email, password: password);
} else {
authResult = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
}
// Auth뿐만 아니라 Cloud Firestore 부분에 추가 시켜주기 위한 부분
await FirebaseFirestore.instance
.collection('users')
.doc(authResult.user?.uid)
.set({
'username': username,
'email': email,
});
} on PlatformException catch (err) {
var message = 'An error occurred, please check your credentials';
if (err.message != null) {
message = err.message!;
}
ScaffoldMessenger.of(ctx).showSnackBar(SnackBar(
content: Text(message),
backgroundColor: Theme.of(ctx).errorColor,
));
// 로그인 회원가입등의 로딩이 끝나는 지점
setState(() {
_isLoading = false;
});
} catch (err) {
print(err);
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).primaryColor,
body: AuthForm(
_submitAuthForm,
_isLoading,
),
);
}
}
Auth_Screen에서 내부 form 부분 분리
5. auth_form.dart 생성
더보기
import 'dart:io';
import 'package:flutter/material.dart';
class AuthForm extends StatefulWidget {
AuthForm(this.submitFn, this.isLoading);
final bool isLoading;
final void Function(
String email,
String password,
String userName,
bool isLogin,
BuildContext ctx,
) submitFn;
@override
State<AuthForm> createState() => _AuthFormState();
}
class _AuthFormState extends State<AuthForm> {
final GlobalKey<FormState> _formKey = GlobalKey();
var _isLogin = true;
var _userEmail = '';
var _userName = '';
var _userPassword = '';
// File _userImageFile;
void _trySubmit() {
final isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (isValid) {
_formKey.currentState!.save();
widget.submitFn(
_userEmail.trim(),
_userName.trim(),
_userPassword.trim(),
_isLogin,
context,
);
}
}
@override
Widget build(BuildContext context) {
return Center(
child: Card(
margin: EdgeInsets.all(20),
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// --- Email ---
TextFormField(
key: ValueKey('email'),
validator: (String? value) {
if (value!.isEmpty || !value.contains('@')) {
return 'Please enter a valid email address.';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email address',
),
onSaved: (value) {
_userEmail = value!;
},
),
// --- UserName ---
if (!_isLogin)
TextFormField(
key: const ValueKey('username'),
validator: (value) {
if (value!.isEmpty || value.length < 4) {
return 'Please enter at least 4 characters';
}
return null;
},
decoration: InputDecoration(
labelText: 'Username',
),
onSaved: (value) {
_userName = value!;
},
),
// --- Password ---
TextFormField(
key: ValueKey('password'),
decoration: InputDecoration(
labelText: 'Password',
),
obscureText: true,
onSaved: (value) {
_userPassword = value!;
},
),
const SizedBox(height: 12),
if (widget.isLoading) const CircularProgressIndicator(),
if (!widget.isLoading)
ElevatedButton(
onPressed: _trySubmit,
child: Text(_isLogin ? 'Login' : 'Signup'),
),
if (!widget.isLoading)
TextButton(
child: Text(_isLogin
? 'Create new account'
: 'I already have an account'),
onPressed: () {
setState(() {
_isLogin = !_isLogin;
});
},
),
],
),
),
),
),
),
);
}
}
6. chat_screen.dart 수정
더보기
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
class ChatScreen extends StatelessWidget {
const ChatScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FlutterChat'),
actions: [
DropdownButton(
underline: Container(),
icon: Icon(
Icons.more_vert,
color: Theme.of(context).primaryIconTheme.color,
),
items: [
DropdownMenuItem(
child: Container(
child: Row(
children: <Widget>[
Icon(Icons.exit_to_app),
SizedBox(width: 8),
Text('Logout'),
],
),
),
value: 'logout',
),
],
onChanged: (itemIdentifier) {
if (itemIdentifier == 'logout') {
FirebaseAuth.instance.signOut();
}
},
),
],
),
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('chats/5faluq62QDSCE83nxKok/message')
.snapshots(),
builder: (ctx, streamSnapshot) {
if (streamSnapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
final documents = streamSnapshot.data!.docs;
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: (ctx, index) => Container(
padding: const EdgeInsets.all(8),
child: Text(documents[index]['text']),
),
);
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
FirebaseFirestore.instance
.collection('chats/5faluq62QDSCE83nxKok/message')
.add({'text': 'This was added by clicking the button!'});
},
),
);
}
}
최종 화면 :