flutter로 중고장터 만들기 Sign_in_up.dart(firebase, provider 사용)
0 - 앞에서 Sign_up.dart을 만들었습니다. 문제점도 많았고 UI도 이쁘지 않았습니다. 문제점을 해결하고 로그인과 회원가입을 동시에 할 수 있도록 provider을 사용해 구현하겠습니다.
1. 목표
Firebase을 연동해 회원가입 및 로그인 페이지를 구현합니다.
로그인 및 회원가입 후에 페이지에 들어올 수 있도록 구현합니다.
Provider을 사용해 한 페이지에서 로그인 및 회원가입을 동시에 할 수 있도록 합니다.
UI 위젯을 디자인에 소질이 없지만 최선을 다해 꾸며본다.
구글 인증과, 이메일 인증을 이용해 회원가입을 구현한다.
2. 구현 내용
Sign_in_up.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_signin_button/flutter_signin_button.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:graduationproject/provider.dart';
import 'package:provider/provider.dart';
import 'methods/toast.dart';
import 'methods/validators.dart';
사용할 라이브러리이다. 이번에는 코드가 좀 깁니다.
class AppState {
bool loading;
AppState(this.loading);
}
로딩을 하게 해주는 class입니다. 앞에서 많이 설명했으니깐 넘어가겠습니다.
class Sign_in_up extends StatefulWidget {
Sign_in_up({Key key}) : super(key: key);
static const routeName = '/Sing_in_up';
@override
_Sign_in_upState createState() => _Sign_in_upState();
}
StatefulWidget으로 만들어 준 후에 route 설정을 합니다. rotue에 대한 자세한 내용은 https://kkungchan.tistory.com/75?category=881648 다음 글에 있습니다.
class _Sign_in_upState extends State<Sign_in_up> {
final app = AppState(false);
final _scaffoldKey = GlobalKey<ScaffoldState>();
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
TextEditingController firstNameInputController = TextEditingController();
TextEditingController lastNameInputController = TextEditingController();
TextEditingController emailInputController = TextEditingController();
TextEditingController passwordInputController = TextEditingController();
TextEditingController confirmPasswordInputController = TextEditingController();
먼저 회원가입과 로그인에 필요한 TextEditingController와 GlobalKey을 선언해 줍니다.
_googleSignIn() async {
final bool isSignedIn = await GoogleSignIn().isSignedIn();
GoogleSignInAccount googleUser;
if (isSignedIn)
googleUser = await GoogleSignIn().signInSilently();
else
googleUser = await GoogleSignIn().signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
idToken: googleAuth.idToken,
accessToken: googleAuth.accessToken,
);
final FirebaseUser user =
(await FirebaseAuth.instance.signInWithCredential(credential)).user;
return user;
}
다음 함수가 구글 로그인과 회원가입을 진행해 줍니다. 버튼으로 다음 함수를 실행해 줍시다.
@override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return ChangeNotifierProvider<ChangeSign>(
create : (_) => ChangeSign(),
child: _DesignLogin(size));
}
flutter에서 가장 중요한 build widget입니다. MediaQuery.of(context).size은 핸드폰 가로길이와 세로 길이를 가져와줍니다. UI를 디자인 할때 중요한 코드입니다. 여기서 ChangeNotifierProvider<ChangeSign>이 나오는데 이는 provider가 알림을 보낼 위젯의 최상위를 지정해주는 코드입니다. 이에 대해서는 밑에서 좀 더 자세히 다루도록 하겠습니다.
지금은 ChangeNotifierProvider<ChangeSign>을 신경쓰지 말고 retrun되는 _DesignLogin()을 보겠습니다. _DesignLogin()위제에 size 변수를 보내줍니다.
return Scaffold(
body: ListView(
//mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Stack(
children: <Widget>[
//초록색 휴지통 아이콘
Container(
margin: EdgeInsets.only(
left: size.width * 0.5,
right: size.width * 0.15,
top: size.height * 0.15,
bottom: size.height * 0.01,
),
height: 120.0,
width: 120.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100.0),
color: Color(0xFF18D191)),
child: Icon(
Icons.delete_forever,
size: 100,
color: Colors.white,
),
),
//파란색 무한대 아이콘
Container(
margin: EdgeInsets.only(
left: size.width * 0.15,
right: size.width * 0.5,
top: size.height * 0.15,
bottom: size.height * 0.01,
),
height: 120.0,
width: 120.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100.0),
color: Color(0xFF45E0EC)),
child: Icon(
Icons.all_inclusive,
size: 100,
color: Colors.white,
),
),
//검은색 동그라미
Container(
margin: EdgeInsets.only(
left: size.width * 0.3,
right: size.width * 0.23,
top: size.height * 0.05,
bottom: size.height * 0.05,
),
height: 150.0,
width: 150.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(150.0),
color: Colors.black),
),
//c글자
Container(
margin: EdgeInsets.only(
left: size.width * 0.34,
right: size.width * 0.33,
top: size.height * 0.04,
bottom: 0.0),
child: Text(
"C",
style: TextStyle(
fontFamily: 'Multilingual',
fontStyle: FontStyle.normal,
fontSize: 150,
color: Colors.white),
),
),
],
),
//C-STYLE
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//C-STYLE
Padding(
padding: const EdgeInsets.all(0),
child: Text(
'C-STYLE',
style: TextStyle(
fontFamily: 'Multilingual',
fontStyle: FontStyle.normal,
fontSize: 80,
color: Color(0xFFFFCE56)),
),
)
],
),
_LoginButton() //로그인 및 회원강비 폼
],
));
}
다음 코드는 로그인 회원가입 페이지를 꾸며주는 UI입니다. 제가 디자인을 잘 모릅니다. 교수님께서 UI를 너무 강조하셔서 그래도 어떻게 이쁘게 만들어 보기 위해서 열심히 꾸며 봤습니다. 설명을 하면 가장 면서 ListView로 스코롤을 내릴 수 있도록 만들었습니다.(ListView가 아닌 Container나 Column으로 전체 화면을 잡게 되면 화면 밑으로 위젯이 생길시 공사중 표시가 뜬다.) Stack위젯을 만들고 먼저 Container로 사각형 모양을 위치하게 한후 borderRadius: BorderRadius.circular(100) 으로 원형을로 깍습니다. 그 위에 아이콘 모양 혹은 글자를 삽입합니다.(Stack으로 위젯을 상위에 두었기 때문에 그 위에 위젯을 그리는 것이 가능하다.) 그 밑에 C-STLYE라는 글자를 적어 앱의 이름을 그립니다..
UI를 설정할 때 팁은 스케지북과 색연필입니다. 여기다가 디자인 하고 싶은 내용을 그리고 그것을 어떻게 구연할 수 있는지를 코드로 변환시킨구 구현하는것이 가장 효율적입니다.(경험상 이야기이다.) 대부분 flutter에서 UI는 Colunm, Stack, Container로 모두 구현할 수 있습니다.
_LoginButton은 로그인과 회원가입을 할 수 있도록 만든 폼입니다. 다음은 _LoginButton() 함수를 구현하겠습니다.
Widget _LoginButton() {
return Consumer<ChangeSign>(
builder: (context, value, child) => Stack(children: <Widget>[
_Email_Password_background(),
//Sign in with Email
if (value.getStandardSign())
_Sign_in_with_Email(),
//or
if (value.getStandardSign())
Sing_In_or(),
//Sign in with Gmail
if (value.getStandardSign())
_Sign_in_with_Gmail(),
//회원이 아니신가요?
if (value.getStandardSign())
do_not_have_accoount(),
//Sign up with Email
if (!value.getStandardSign())
SignUp_with_Email(),
if (!value.getStandardSign())
Sing_Up_or(),
if (!value.getStandardSign())
SignUp_with_Google(),
if (!value.getStandardSign())
Change_Login()
]),
);
}
뭔가 위젯과 TextForm이 잔득 나올 줄 알았는데 알 수 없는 Consumer<ChangeSign> 함수와 if문이 잔득 나왔다. 이부분이 provider을 쓴 부분인데 이 함수를 이해하기 위해서는 먼저 provider을 이해 해야 한다.
*Provider
Provider는 bloc패턴에서 나온 개념으로 bloc을 구현 할려면 class을 4개 이상 만들어야 하는 불편함이 있어 Provider가 나왔다.
provider을 사용하는 이유는 3가지이다.
1. 관심사의 분리
2. 다른 페이지의 데이터 공유
3. 간결한 코드
이 세가지를 위해서 provider을 사용한다. 정리하면 짧은 코드로 한 클래스에서 너무 많은 일을 하지 않도록 도와주고 다른 페이지에서 서로 데이터를 공유 할 수 있도록 한다. 직접 구현한 코드를 보면서 좀 더 자세히 사용법을 느껴보록 하자
provider.dart
import 'package:flutter/cupertino.dart';
class ChangeSign with ChangeNotifier{
bool _StandardSign = true;
var _SelectedValue = int.parse('1');
var _index = 0;
bool getStandardSign() => _StandardSign;
dynamic getSelectedVale() => _SelectedValue;
dynamic getIndex() => _index;
void Change_Sign(){
_StandardSign = !_StandardSign;
notifyListeners();
}
void ChangSelectedVale(int selectnum){
_SelectedValue = selectnum;
notifyListeners();
}
}
Provider의 짧지만 강력한 코드이다. 먼저 설명을 하면 공유할 데이터를 선언했다. _StandardSign, _SelectedValue 을 선언했다.(_index은 provider 클래스 안에서만 쓰는 변수라 사실 get을 안해줘도 되는데 혹시 몰라서 해줬다.) 그리고 쉽게 접근하지 못해서 get함수를 선언했다.(getStandardSign() => _StandardSign;으로 선언해주면 _StandardSign을 다른 class에서 참조 할 수 없고 getStandardSign()함수를 이용해야만 참조 가능하다.) 지금 Sign_in_up에서 중요한 함수는 Change_Sign()함수이다.(ChangeSelectedValue()함수는 add() 페이지에서 사용함으로 그 페이지를 구현할 때 설명하겠다.) Chang_Sign()함수는 _StandardSign bool값을 변경시켜주는 역할을 한다. 즉 _StandardSign값이 True면 False로 False면 True로 변경시켜 주는 것이다. 자 이제 이 provider가 어떤 역할을 해주는지 다시 Sign_in_up함수로 돌아가보자
Widget _LoginButton() {
return Consumer<ChangeSign>(
builder: (context, value, child) => Stack(children: <Widget>[
_Email_Password_background(),
//Sign in with Email
if (value.getStandardSign())
_Sign_in_with_Email(),
//or
if (value.getStandardSign())
Sing_In_or(),
//Sign in with Gmail
if (value.getStandardSign())
_Sign_in_with_Gmail(),
//회원이 아니신가요?
if (value.getStandardSign())
do_not_have_accoount(),
//Sign up with Email
if (!value.getStandardSign())
SignUp_with_Email(),
if (!value.getStandardSign())
Sing_Up_or(),
if (!value.getStandardSign())
SignUp_with_Google(),
if (!value.getStandardSign())
Change_Login()
]),
);
}
Provider을 사용하는 방법은 두가지가 있는데 provider랑 Consumer이다. Consumer는 context을 스스로 찾기 때문에 context가 없어도 사용할 수 있다. 나는 두가지 모두 사용해보고 싶었는데 어디서 context 오류가 있는지 provider을 사용하면 계속 오류가 떠서 하는 수 없이 Consumer을 사용했다. Consumer을 사용하면 value을 이용해 provider의 변수를 사욯할 수 있다. if문을 이용해 StandardSign이 true일때는 로그인 버튼을 StandardSign이 false일때는 회원가입 버튼을 출력해 줄 수 있다.
이 코드만 보면 사실 provider을 사용하는 이유를 잘 모르겠다. 그냥 class안에다 bool변수 하나를 선언해주고 사용하면 된는거 아닌가? 라는 생각을 하게 된다. 다음 _Email_Password_background()함수를 보면 좀 더 명확하게 provider을 사용하는 이유를 알 수 있게 된다.
Widget _Email_Password_background() {
return Padding(
// card에 외부 여백을 주는 padding
padding: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10),
child: Consumer<ChangeSign>(
builder: (context, value, child) => Card(
color:
value.getStandardSign() ? Color(0xFFF3E5F5) : Color(0xFFFFECB3),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 6,
child: value.getStandardSign()
? Login_inout_Form()
: SignUp_inout_Form()),
),
);
}
버튼 위에 그려지는 card 폼이다. Padding으로 감싸져 있고 Consumer을 사용해 StandardSign이 True면 분홍색 False면 노란색 계열에 색을 그려준다. 그리고 또한 True면 Login_inout_Form()을 False면 SignUp_inout_Form()위젯을 호출해 준다. 아직도 이해가 잘 안갈 수 있다. 왜 아직도 굳이 provider을 사용해야 하는지 모른다면 flutter앱의 작동 방식을 떠올려야 한다.
만약에 내가 class에 그냥 StandardSign을 선언하고 ChangeSign() 함수를 통해 StandardSign bool값을 변환해줄 수 있도록 코딩한 후에 UI에서 "회원이 아니십니까"을 사용자가 클릭하게 되면 ChangeSing()함수를 호출해 StandardSign bool값을 변화 주도록 구현했다고 하자 실제로 사용자가 "회원이 아니십까" 를 클릭하면 위젯이 변화가 있을까? 아니다!! 변화가 없다! 왜냐하면 앱은 다시 그리라는 명령은 받은 적이 없기 때문에 StandardSign 값은 변하지만 실제 위젯은 변화가 없는 것이다!! Provider의 가장 큰 매력이 이것이라고 생각한다. 호출하면 호출하는 순간 하위 위젯한테 모두 알람이 간다. 내가 변했으니 모두 다시 그려! 라고 내가 provider을 구현하게 된 가장 큰 이유이다. 그 알림이 가는 하위 위젯도 설정할 수 있는데 바로 앞에서 보았던 다음 코드로 가능하다.
@override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return ChangeNotifierProvider<ChangeSign>(
create: (_) => ChangeSign(), child: _DesignLogin(size));
}
처음에 넘어가자고 말했던 ChangeNotifierProvider로 감싼 위젯이 알람을 보내는 가장 상위 위젯이다. 이것보다 하위 인 위젯은 모두 provider 변수가 변하는 순간 알람을 받고 UI를 다시 그린다.
동영상 이었다면 구현된 화면을 보면서 더 잘 설명할 수 있을 텐데 아쉽다.
다음은 회원가입과 로그인 폼이다. TextFormField 위젯을 주로 이루어져 있으며 formKey와 TextEditingController를 구현한다.
Widget SignUp_inout_Form() {
return Padding(
padding: const EdgeInsets.only(
left: 12.0, right: 12.0, top: 12.0, bottom: 250),
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: TextFormField(
decoration: InputDecoration(
labelText: '성',
hintText: '성을 입력하세요',
border: OutlineInputBorder(),
),
controller: firstNameInputController,
validator: (value) {
if (value.isEmpty)
return 'Please enter a valid first name.';
return null;
},
),
),
SizedBox(
width: 10,
),
Expanded(
child: TextFormField(
decoration: InputDecoration(
labelText: '이름',
hintText: '이름을 입력하세요',
border: OutlineInputBorder(),
),
controller: lastNameInputController,
validator: (value) {
if (value.length < 1)
return 'Please enter a valid last name.';
return null;
},
),
),
],
),
SizedBox(
height: 10,
),
TextFormField(
decoration: InputDecoration(
labelText: '이메일',
hintText: "이메일을 입력해주세요",
border: OutlineInputBorder()),
controller: emailInputController,
keyboardType: TextInputType.emailAddress,
validator: emailValidator, //validator을 통해 이메일을 검사합니다.
),
SizedBox(
height: 10,
),
TextFormField(
decoration: InputDecoration(
labelText: '비밀번호',
hintText: "비밀번호를 입력해주세요",
border: OutlineInputBorder() // 비밀번호를 가리게 해줌
),
controller: passwordInputController,
obscureText: true,
validator: passwordValidator,
),
SizedBox(
height: 10,
),
TextFormField(
decoration: InputDecoration(
labelText: '비밀번호',
hintText: "비밀번호를 다시 입력해주세요",
border: OutlineInputBorder()),
controller: confirmPasswordInputController,
obscureText: true,
validator: passwordValidator,
),
]),
),
),
);
}
Widget Login_inout_Form() {
return Padding(
// Form에 외부 여백을 주는 padding
padding: const EdgeInsets.only(
left: 12.0, right: 12.0, top: 12.0, bottom: 250),
child: Form(
child: Column(
children: <Widget>[
//email input
TextFormField(
decoration: InputDecoration(
icon: Icon(Icons.account_circle),
labelText: 'Email',
hintText: '이메일을 입력하세요',
border: OutlineInputBorder(),
),
controller: emailController,
keyboardType: TextInputType.emailAddress,
),
SizedBox(
height: 10,
),
//password input
TextFormField(
decoration: InputDecoration(
icon: Icon(Icons.vpn_key),
labelText: 'Password',
hintText: '비밀번호를 입력하세요',
border: OutlineInputBorder(),
),
controller: passwordController,
obscureText: true, // 비밀번호가 안보이는 코드
),
],
),
),
);
}
크게 설명할 내용은 없다 자세한 사항은 https://kkungchan.tistory.com/68?category=881648여기를 참조하면 된다.
Widget _Sign_in_with_Email() {
return Positioned(
left: 50,
right: 50,
top: 170,
bottom: 200,
child: SignInButton(
Buttons.Email,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
onPressed: () async {
//예외 처리
try {
setState(() => app.loading = true);
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: emailController.text,
password: passwordController.text,
);
Navigator.pushReplacementNamed(context, '/auth');
} catch (e) {
toastError(_scaffoldKey, e);
} finally {
setState(() => app.loading = false);
}
},
),
);
}
Widget Sing_In_or() {
return Positioned(
left: 250,
right: 260,
top: 220,
bottom: 160,
child: Text(
'or',
style: TextStyle(fontSize: 20),
));
}
Widget _Sign_in_with_Gmail() {
return Positioned(
left: 50,
right: 50,
top: 260,
bottom: 110,
child: SignInButton(
Buttons.Google,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
onPressed: () async {
try {
setState(() => app.loading = true);
await _googleSignIn();
Navigator.pushReplacementNamed(context, '/auth');
} catch (e) {
toastError(_scaffoldKey, e);
} finally {
setState(() => app.loading = false);
}
},
));
}
Widget do_not_have_accoount() {
return Positioned(
left: 50,
right: 50,
top: 320,
bottom: 30,
child: Consumer<ChangeSign>(
builder: (context, value, child) => FlatButton(
child: Text(
'아직 회원이 아니신가요?',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
fontFamily: 'Nanum Gothic',
),
),
onPressed: () {
value.Change_Sign();
}),
),
);
}
다음은 firebase을 통해 로그인 하는 함수이다. 기본적으로 button 위젯이며 do_not_have_account() 에서는 value.Change_Sign()을 호출해 불리언 값을 변경시켜 알람을 보내준다. 나머지 위젯은 로그인에 성공하면 네비게이터를 통해 ./auth로 이동해 로그인과 이메일 인증 여부에 따라 backPage로 보내준다. 다음같이 구현함으로써 회원가입과 로그인이 안된 회원이 앱에 접근하는 것을 방지할 수 있다.
Widget SignUp_with_Email() {
return Positioned(
left: 50,
right: 50,
top: 330,
bottom: 180,
child: SignInButtonBuilder(
text: 'Sign up with Email',
icon: Icons.email,
backgroundColor: Colors.blueGrey[700],
onPressed: () async {
if (!_formKey.currentState.validate()) return; // 버튼 눌렀을떼 validate확인
//비밀번호가 같은지 확인
if (passwordInputController.text !=
confirmPasswordInputController.text) {
toastError(
_scaffoldKey,
PlatformException(
code: 'signup', message: '비밀번호와 비밀번호 확인이 다릅니다.'));
return;
}
try {
setState(() => app.loading = true);
final fires = await FirebaseAuth.instance
.createUserWithEmailAndPassword(
email: emailInputController.text,
password: passwordInputController.text);
//firebase에서 기본으로 제공하는 user 업데이트 유저 이름, 유저 이미지
final userInfo = UserUpdateInfo();
userInfo.photoUrl =
'https://ssl.gstatic.com/ui/v1/icons/mail/rfr/logo_gmail_lockup_default_1x.png';
userInfo.displayName = firstNameInputController.text +
' ' +
lastNameInputController.text;
await fires.user.updateProfile(userInfo);
await fires.user.reload(); //사용자 갱신 필요
await fires.user.sendEmailVerification(); //이메일인증을 위해 이메일에 메일을 전송함
Navigator.pushNamedAndRemoveUntil(
context, '/auth', (route) => false);
} catch (e) {
toastError(_scaffoldKey, e);
print(e);
} finally {
if (mounted) setState(() => app.loading = false);
}
},
),
);
}
Widget Sing_Up_or() {
return Positioned(
left: 250,
right: 260,
top: 390,
bottom: 130,
child: Text(
'or',
style: TextStyle(fontSize: 20),
));
}
Widget SignUp_with_Google() {
return Positioned(
left: 50,
right: 50,
top: 420,
bottom: 90,
child: SignInButton(
Buttons.Google,
onPressed: () async {
try {
setState(() => app.loading = true);
await _googleSignIn();
Navigator.pushNamedAndRemoveUntil(
context, '/auth', (route) => false);
} catch (e) {
toastError(_scaffoldKey, e);
} finally {
setState(() => app.loading = false);
}
},
),
);
}
Widget Change_Login() {
return Consumer<ChangeSign>(
builder: (context, value, child) => Positioned(
left: 20,
right: 50,
top: 490,
bottom: 50,
child: FlatButton(
child: Text(
'로그인하러 가기',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
onPressed: () {
value.Change_Sign();
}),
),
);
}
}
다음은 회원가입 버튼이다. 회원 가입 버튼을 누르게 되면 입력값이 맞는지 확인한 후에 맞으면 URL로 이메일을 보내고 auth.dart 페이지로 보내준다. 이유는 auth.dart 페이지를 구현한 이유를 다음 페이지에서 보면 된다. https://kkungchan.tistory.com/73?category=881648 Change_Login() 함수 역시 위에 do_not_have_account() 함수와 같은 기능을 한다고 생각하면 된다.
3. 전제 소스 코드 - Sign_in_up.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_signin_button/flutter_signin_button.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:graduationproject/provider.dart';
import 'package:provider/provider.dart';
import 'methods/toast.dart';
import 'methods/validators.dart';
class AppState {
bool loading;
AppState(this.loading);
}
class Sign_in_up extends StatefulWidget {
Sign_in_up({Key key}) : super(key: key);
static const routeName = '/Sing_in_up';
@override
_Sign_in_upState createState() => _Sign_in_upState();
}
class _Sign_in_upState extends State<Sign_in_up> {
final app = AppState(false);
final _scaffoldKey = GlobalKey<ScaffoldState>();
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
TextEditingController firstNameInputController = TextEditingController();
TextEditingController lastNameInputController = TextEditingController();
TextEditingController emailInputController = TextEditingController();
TextEditingController passwordInputController = TextEditingController();
TextEditingController confirmPasswordInputController =
TextEditingController();
Widget _loadingWidget() {
return Center(
child: CircularProgressIndicator(),
);
}
_googleSignIn() async {
final bool isSignedIn = await GoogleSignIn().isSignedIn();
GoogleSignInAccount googleUser;
if (isSignedIn)
googleUser = await GoogleSignIn().signInSilently();
else
googleUser = await GoogleSignIn().signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
idToken: googleAuth.idToken,
accessToken: googleAuth.accessToken,
);
final FirebaseUser user =
(await FirebaseAuth.instance.signInWithCredential(credential)).user;
return user;
}
_buildLoading() {
return Center(
child: CircularProgressIndicator(),
);
}
@override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return ChangeNotifierProvider<ChangeSign>(
create: (_) => ChangeSign(), child: _DesignLogin(size));
}
Widget _DesignLogin(size) {
return Scaffold(
body: ListView(
//mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Stack(
children: <Widget>[
//초록색 휴지통 아이콘
Container(
margin: EdgeInsets.only(
left: size.width * 0.5,
right: size.width * 0.15,
top: size.height * 0.15,
bottom: size.height * 0.01,
),
height: 120.0,
width: 120.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100.0),
color: Color(0xFF18D191)),
child: Icon(
Icons.delete_forever,
size: 100,
color: Colors.white,
),
),
//파란색 무한대 아이콘
Container(
margin: EdgeInsets.only(
left: size.width * 0.15,
right: size.width * 0.5,
top: size.height * 0.15,
bottom: size.height * 0.01,
),
height: 120.0,
width: 120.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100.0),
color: Color(0xFF45E0EC)),
child: Icon(
Icons.all_inclusive,
size: 100,
color: Colors.white,
),
),
//검은색 동그라미
Container(
margin: EdgeInsets.only(
left: size.width * 0.3,
right: size.width * 0.23,
top: size.height * 0.05,
bottom: size.height * 0.05,
),
height: 150.0,
width: 150.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(150.0),
color: Colors.black),
),
//c글자
Container(
margin: EdgeInsets.only(
left: size.width * 0.34,
right: size.width * 0.33,
top: size.height * 0.04,
bottom: 0.0),
child: Text(
"C",
style: TextStyle(
fontFamily: 'Multilingual',
fontStyle: FontStyle.normal,
fontSize: 150,
color: Colors.white),
),
),
],
),
//C-STYLE
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//C-STYLE
Padding(
padding: const EdgeInsets.all(0),
child: Text(
'C-STYLE',
style: TextStyle(
fontFamily: 'Multilingual',
fontStyle: FontStyle.normal,
fontSize: 80,
color: Color(0xFFFFCE56)),
),
)
],
),
_LoginButton() //로그인 및 회원강비 폼
],
));
}
Widget _LoginButton() {
return Consumer<ChangeSign>(
builder: (context, value, child) => Stack(children: <Widget>[
_Email_Password_background(),
//Sign in with Email
if (value.getStandardSign())
_Sign_in_with_Email(),
//or
if (value.getStandardSign())
Sing_In_or(),
//Sign in with Gmail
if (value.getStandardSign())
_Sign_in_with_Gmail(),
//회원이 아니신가요?
if (value.getStandardSign())
do_not_have_accoount(),
//Sign up with Email
if (!value.getStandardSign())
SignUp_with_Email(),
if (!value.getStandardSign())
Sing_Up_or(),
if (!value.getStandardSign())
SignUp_with_Google(),
if (!value.getStandardSign())
Change_Login()
]),
);
}
Widget _Email_Password_background() {
return Padding(
// card에 외부 여백을 주는 padding
padding: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10),
child: Consumer<ChangeSign>(
builder: (context, value, child) => Card(
color:
value.getStandardSign() ? Color(0xFFF3E5F5) : Color(0xFFFFECB3),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 6,
child: value.getStandardSign()
? Login_inout_Form()
: SignUp_inout_Form()),
),
);
}
Widget SignUp_inout_Form() {
return Padding(
padding: const EdgeInsets.only(
left: 12.0, right: 12.0, top: 12.0, bottom: 250),
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: TextFormField(
decoration: InputDecoration(
labelText: '성',
hintText: '성을 입력하세요',
border: OutlineInputBorder(),
),
controller: firstNameInputController,
validator: (value) {
if (value.isEmpty)
return 'Please enter a valid first name.';
return null;
},
),
),
SizedBox(
width: 10,
),
Expanded(
child: TextFormField(
decoration: InputDecoration(
labelText: '이름',
hintText: '이름을 입력하세요',
border: OutlineInputBorder(),
),
controller: lastNameInputController,
validator: (value) {
if (value.length < 1)
return 'Please enter a valid last name.';
return null;
},
),
),
],
),
SizedBox(
height: 10,
),
TextFormField(
decoration: InputDecoration(
labelText: '이메일',
hintText: "이메일을 입력해주세요",
border: OutlineInputBorder()),
controller: emailInputController,
keyboardType: TextInputType.emailAddress,
validator: emailValidator, //validator을 통해 이메일을 검사합니다.
),
SizedBox(
height: 10,
),
TextFormField(
decoration: InputDecoration(
labelText: '비밀번호',
hintText: "비밀번호를 입력해주세요",
border: OutlineInputBorder() // 비밀번호를 가리게 해줌
),
controller: passwordInputController,
obscureText: true,
validator: passwordValidator,
),
SizedBox(
height: 10,
),
TextFormField(
decoration: InputDecoration(
labelText: '비밀번호',
hintText: "비밀번호를 다시 입력해주세요",
border: OutlineInputBorder()),
controller: confirmPasswordInputController,
obscureText: true,
validator: passwordValidator,
),
]),
),
),
);
}
Widget Sign_up_Alert() {
return AlertDialog(
title: Text('입력 오류'),
content: (SingleChildScrollView(
child: ListBody(
children: <Widget>[Text('입력이 잘못되었습니다.'), Text('다시 입력해주세요')],
),
)),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: () {
//다일러그 닫기
Navigator.of(context).pop();
},
)
],
);
}
Widget Login_inout_Form() {
return Padding(
// Form에 외부 여백을 주는 padding
padding: const EdgeInsets.only(
left: 12.0, right: 12.0, top: 12.0, bottom: 250),
child: Form(
child: Column(
children: <Widget>[
//email input
TextFormField(
decoration: InputDecoration(
icon: Icon(Icons.account_circle),
labelText: 'Email',
hintText: '이메일을 입력하세요',
border: OutlineInputBorder(),
),
controller: emailController,
keyboardType: TextInputType.emailAddress,
),
SizedBox(
height: 10,
),
//password input
TextFormField(
decoration: InputDecoration(
icon: Icon(Icons.vpn_key),
labelText: 'Password',
hintText: '비밀번호를 입력하세요',
border: OutlineInputBorder(),
),
controller: passwordController,
obscureText: true, // 비밀번호가 안보이는 코드
),
],
),
),
);
}
Widget _Sign_in_with_Email() {
return Positioned(
left: 50,
right: 50,
top: 170,
bottom: 200,
child: SignInButton(
Buttons.Email,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
onPressed: () async {
//예외 처리
try {
setState(() => app.loading = true);
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: emailController.text,
password: passwordController.text,
);
Navigator.pushReplacementNamed(context, '/auth');
} catch (e) {
toastError(_scaffoldKey, e);
} finally {
setState(() => app.loading = false);
}
},
),
);
}
Widget Sing_In_or() {
return Positioned(
left: 250,
right: 260,
top: 220,
bottom: 160,
child: Text(
'or',
style: TextStyle(fontSize: 20),
));
}
Widget _Sign_in_with_Gmail() {
return Positioned(
left: 50,
right: 50,
top: 260,
bottom: 110,
child: SignInButton(
Buttons.Google,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
onPressed: () async {
try {
setState(() => app.loading = true);
await _googleSignIn();
Navigator.pushReplacementNamed(context, '/auth');
} catch (e) {
toastError(_scaffoldKey, e);
} finally {
setState(() => app.loading = false);
}
},
));
}
Widget do_not_have_accoount() {
return Positioned(
left: 50,
right: 50,
top: 320,
bottom: 30,
child: Consumer<ChangeSign>(
builder: (context, value, child) => FlatButton(
child: Text(
'아직 회원이 아니신가요?',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
fontFamily: 'Nanum Gothic',
),
),
onPressed: () {
value.Change_Sign();
}),
),
);
}
Widget SignUp_with_Email() {
return Positioned(
left: 50,
right: 50,
top: 330,
bottom: 180,
child: SignInButtonBuilder(
text: 'Sign up with Email',
icon: Icons.email,
backgroundColor: Colors.blueGrey[700],
onPressed: () async {
if (!_formKey.currentState.validate()) return; // 버튼 눌렀을떼 validate확인
//비밀번호가 같은지 확인
if (passwordInputController.text !=
confirmPasswordInputController.text) {
toastError(
_scaffoldKey,
PlatformException(
code: 'signup', message: '비밀번호와 비밀번호 확인이 다릅니다.'));
return;
}
try {
setState(() => app.loading = true);
final fires = await FirebaseAuth.instance
.createUserWithEmailAndPassword(
email: emailInputController.text,
password: passwordInputController.text);
//firebase에서 기본으로 제공하는 user 업데이트 유저 이름, 유저 이미지
final userInfo = UserUpdateInfo();
userInfo.photoUrl =
'https://ssl.gstatic.com/ui/v1/icons/mail/rfr/logo_gmail_lockup_default_1x.png';
userInfo.displayName = firstNameInputController.text +
' ' +
lastNameInputController.text;
await fires.user.updateProfile(userInfo);
await fires.user.reload(); //사용자 갱신 필요
await fires.user.sendEmailVerification(); //이메일인증을 위해 이메일에 메일을 전송함
Navigator.pushNamedAndRemoveUntil(
context, '/auth', (route) => false);
} catch (e) {
toastError(_scaffoldKey, e);
print(e);
} finally {
if (mounted) setState(() => app.loading = false);
}
},
),
);
}
Widget Sing_Up_or() {
return Positioned(
left: 250,
right: 260,
top: 390,
bottom: 130,
child: Text(
'or',
style: TextStyle(fontSize: 20),
));
}
Widget SignUp_with_Google() {
return Positioned(
left: 50,
right: 50,
top: 420,
bottom: 90,
child: SignInButton(
Buttons.Google,
onPressed: () async {
try {
setState(() => app.loading = true);
await _googleSignIn();
Navigator.pushNamedAndRemoveUntil(
context, '/auth', (route) => false);
} catch (e) {
toastError(_scaffoldKey, e);
} finally {
setState(() => app.loading = false);
}
},
),
);
}
Widget Change_Login() {
return Consumer<ChangeSign>(
builder: (context, value, child) => Positioned(
left: 20,
right: 50,
top: 490,
bottom: 50,
child: FlatButton(
child: Text(
'로그인하러 가기',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
onPressed: () {
value.Change_Sign();
}),
),
);
}
}
4. 구현된 화면
_StandardSign 값이 True 일 때 Sign_in_up.dart 페이지의 UI이다.
아직 회원이 아니신가요? 을 클릭해 _StandardSign 값이 False로 변했을 때 다시 그려진 Sign_in_up.dart 페이지 이다.
5. 앞으로 구현할 내용
backPage을 구현한다.
firebase 데이터 베이스와 storage을 연동하여 사진을 올리고 가져올 수 있도록 구현한다.
출처 :
Flutter Documentation
The landing page for Flutter documentation.
flutter.dev
memi’s startup
Development logs
fkkmemi.github.io
'전공_STUDY > Flutter 어플 개발' 카테고리의 다른 글
flutter로 중고장터 만들기 add.dart(provider, firebase, firestorage, 이미지 압축 사용) (0) | 2020.07.22 |
---|---|
flutter로 중고장터 만들기 backPage.dart (0) | 2020.06.30 |
flutter로 중고장터 만들기 main.dart 수정(route 사용) (0) | 2020.06.26 |
auth.dart 구현 (2) | 2020.05.30 |
Splash.dart 구현과 main.dart의 코드 정리 (1) | 2020.05.29 |