반응형
포스팅에서 진행한 환경
- OS : Windows 10
- IDE : Visual Studio Code
- Emulator : Pixel 5 API 27 (Android-x86 emulator), Windows (windows-x64)
이번 포스팅에서는 로그인과 로그아웃을 위해 아래와 같은 내용을 다룹니다.
- 로그인/로그아웃 기능 구현
- 회원가입(계정 생성) 기능 구현
다른 설명이 필요하면 아래의 링크를 참고해주세요.
- 01. Flutter - ListView, Card, Navigator - 스크롤 가능한 목록 표시, 목록 선택시 특정 목록 내용 보여주기
- 02. Flutter - StatefulWidget 활용 - 실시간 검색 기능 구현하기
- 03. Flutter - Floating Action Button, showDialog - 플로팅 액션 버튼으로 특정 작업 수행하기(리스트뷰 항목 추가하기)
- 04. Flutter - BottomNavigationBar - 다른 페이지로 이동하기(화면 전환)
- 05. Flutter - 파일 분리하기, class 나누기, 위젯 리소스화하기
포스팅에서 다루는 예시 프로젝트는 아래의 깃허브 링크에서 다운로드 받을 수 있습니다.
'6_add_login_with_shared_preferences_and_mysql' 폴더를 확인해주세요.
회원가입(계정 생성) 기능 구현
- 사용자가 입력한 아이디가 존재하는지 확인
- confirmIdCheck 함수를 통해 사용자가 입력한 아이디가 DB에 존재하는지 체크
- confirmIdCheck 함수는 loginDB.dart 파일에서 정의한 함수임
- 아이디 중복이 확인될 경우 AlertDialog 위젯을 통해 중복 알림 메시지 출력
- 입력한 비밀번호와 재확인 비밀번호가 같은지 확인
- 비밀번호, 비밀번호 재확인 텍스트필드의 값이 동일한지 조건문으로 체크
- 비밀번호가 서로 다를 경우 AlertDialog 위젯을 통해 비밀번호가 같지 않다는 메시지 출력
- 이상 없을 경우 DB에 계정 정보 등록
- insertMember 함수를 통해 사용자의 아이디와 비밀번호를 DB에 추가
- insertMember 함수는 loginDB.dart 파일에서 정의한 함수임
- 비밀번호는 암호화되어 저장 됨
/* memberRegisterPage.dart */
// 계정 생성 버튼
ElevatedButton(
onPressed: () async {
// ID 중복확인
final idCheck =
await confirmIdCheck(userIdController.text);
print('idCheck : $idCheck');
// 아이디가 중복이면 1 값 반환, 중복이 아니면 0 값 반환
if (idCheck != '0') {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('알림'),
content: Text('입력한 아이디가 이미 존재합니다.'),
actions: [
TextButton(
child: Text('닫기'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
// 비밀번호가 다를 경우
else if (passwordController.text !=
passwordVerifyingController.text) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('알림'),
content: Text('입력한 비밀번호가 같지 않습니다.'),
actions: [
TextButton(
child: Text('닫기'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
// DB에 계정 정보 등록
else {
insertMember(userIdController.text,
passwordController.text);
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('알림'),
content: Text('아이디가 생성되었습니다.'),
actions: [
TextButton(
child: Text('닫기'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
},
child: Text('계정 생성'),
),
로그인 기능 구현
- 로그인 버튼을 누르면 아이디와 비밀번호가 올바른지 확인
- login 함수를 통해 유저 식별
- login 함수는 loginDB.dart 파일에서 정의한 함수임
- 유저의 정보가 올바르면 메인 페이지로 이동
- Navigator.push 를 통해 지정한 페이지로 이동
- 이동할 페이지는 header&fotter.dart 파일에서 지정한 페이지로 이동 (MyAppPage)
/* loginMainPage.dart */
// 로그인 버튼
ElevatedButton(
onPressed: () async {
final loginCheck = await login(
userIdController.text, passwordController.text);
print(loginCheck);
// 로그인 확인
if (loginCheck == '-1') {
print('로그인 실패');
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('알림'),
content: Text('아이디 또는 비밀번호가 올바르지 않습니다.'),
actions: [
TextButton(
child: Text('닫기'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
} else {
print('로그인 성공');
// 메인 페이지로 이동
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyAppPage(),
),
);
}
},
child: Text('로그인'),
),
로그아웃 기능 구현
- 로그인시 이동할 메인 페이지를 다시 로그인 페이지로 이동시키는 코드 추가
- 이 포스팅에서는 앱 바에 뒤로가기 버튼을 추가하여 로그인 페이지로 이동하는 식으로 구현하였습니다.
- 로그인하면 이동 될 페이지 : MyAppPage (header&footer.dart)
- 로그아웃하면 이동 될 페이지 : LoginMainPage (loginMainPage.dart)
/* header&fotter.dart */
// 앱 바에 표시
CupertinoNavigationBar(
middle: const Text('Title'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
CupertinoButton(
padding: EdgeInsets.zero,
child: const Icon(
CupertinoIcons.arrowshape_turn_up_left,
size: 30,
),
// 클릭시 로그아웃 여부 확인
onPressed: () {
showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: const Text('알림'),
content: const Text('로그아웃하시겠습니까?'),
actions: <CupertinoDialogAction>[
CupertinoDialogAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(context),
child: const Text('아니오'),
),
CupertinoDialogAction(
isDestructiveAction: true,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => LoginMainPage(),
),
);
},
child: Text('예'),
),
],
),
);
},
),
],
),
),
전체 소스 코드
memberRegisterPage.dart 코드 보기
더보기
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_memo_app/loginPage/loginDB.dart';
class MemberRegisterPage extends StatefulWidget {
const MemberRegisterPage({Key? key}) : super(key: key);
@override
State<MemberRegisterPage> createState() => _MemberRegisterState();
}
class _MemberRegisterState extends State<MemberRegisterPage> {
// 유저의 아이디와 비밀번호의 정보 저장
final TextEditingController userIdController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final TextEditingController passwordVerifyingController =
TextEditingController();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
// 아이디 입력 텍스트필드
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 300,
child: CupertinoTextField(
controller: userIdController,
placeholder: '아이디를 입력해주세요',
textAlign: TextAlign.center,
),
),
),
// 비밀번호 입력 텍스트필드
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 300,
child: CupertinoTextField(
controller: passwordController,
placeholder: '비밀번호를 입력해주세요',
textAlign: TextAlign.center,
obscureText: true,
),
),
),
// 비밀번호 재확인 텍스트필드
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 300,
child: CupertinoTextField(
controller: passwordVerifyingController,
placeholder: '비밀번호를 다시 입력해주세요',
textAlign: TextAlign.center,
obscureText: true,
),
),
),
// 로그인 페이지로 돌아가기
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 95,
child: OutlinedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('뒤로 가기'),
),
),
Text(' '),
// 계정 생성 버튼
SizedBox(
width: 195,
child: ElevatedButton(
onPressed: () async {
// ID 중복확인
final idCheck =
await confirmIdCheck(userIdController.text);
print('idCheck : $idCheck');
// 아이디가 중복이면 1 값 반환, 중복이 아니면 0 값 반환
if (idCheck != '0') {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('알림'),
content: Text('입력한 아이디가 이미 존재합니다.'),
actions: [
TextButton(
child: Text('닫기'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
// 비밀번호가 다를 경우
else if (passwordController.text !=
passwordVerifyingController.text) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('알림'),
content: Text('입력한 비밀번호가 같지 않습니다.'),
actions: [
TextButton(
child: Text('닫기'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
// DB에 계정 정보 등록
else {
insertMember(userIdController.text,
passwordController.text);
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('알림'),
content: Text('아이디가 생성되었습니다.'),
actions: [
TextButton(
child: Text('닫기'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
},
child: Text('계정 생성'),
),
),
],
),
),
],
),
),
),
),
);
}
}
loginMainPage.dart 코드 보기
더보기
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_memo_app/header&footer.dart';
import 'package:flutter_memo_app/loginPage/loginDB.dart';
import 'package:flutter_memo_app/loginPage/memberRegisterPage.dart';
import 'package:shared_preferences/shared_preferences.dart';
// 로그인 페이지
class LoginMainPage extends StatelessWidget {
const LoginMainPage({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: LoginPage(),
);
}
}
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginState();
}
class _LoginState extends State<LoginPage> {
// 자동 로그인 여부
bool switchValue = false;
// 아이디와 비밀번호 정보
final TextEditingController userIdController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// ID 입력 텍스트필드
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 300,
child: CupertinoTextField(
controller: userIdController,
placeholder: '아이디를 입력해주세요',
textAlign: TextAlign.center,
),
),
),
// 비밀번호 입력 텍스트필드
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 300,
child: CupertinoTextField(
controller: passwordController,
placeholder: '비밀번호를 입력해주세요',
textAlign: TextAlign.center,
obscureText: true,
),
),
),
// 자동 로그인 확인 토글 스위치
SizedBox(
width: 300,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'자동로그인 ',
style: TextStyle(
color: CupertinoColors.activeBlue,
fontWeight: FontWeight.bold),
),
CupertinoSwitch(
// 부울 값으로 스위치 토글 (value)
value: switchValue,
activeColor: CupertinoColors.activeBlue,
onChanged: (bool? value) {
// 스위치가 토글될 때 실행될 코드
setState(() {
switchValue = value ?? false;
});
},
),
Text(' '),
// 계정 생성 페이지로 이동하는 버튼
OutlinedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MemberRegisterPage(),
),
);
},
child: Text(
'계정생성',
),
),
],
),
),
// 로그인 버튼
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 250,
child: ElevatedButton(
onPressed: () async {
final loginCheck = await login(
userIdController.text, passwordController.text);
print(loginCheck);
// 로그인 확인
if (loginCheck == '-1') {
print('로그인 실패');
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('알림'),
content: Text('아이디 또는 비밀번호가 올바르지 않습니다.'),
actions: [
TextButton(
child: Text('닫기'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
} else {
print('로그인 성공');
// 메인 페이지로 이동
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyAppPage(),
),
);
}
},
child: Text('로그인'),
),
),
),
],
),
),
),
),
);
}
}
header&footer.dart 코드 보기
더보기
// 기본 홈
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_memo_app/loginPage/loginMainPage.dart';
import 'communityPage/communityMainPage.dart';
import 'memoPage/memoMainPage.dart';
import 'myInfoPage/myInfoMainPage.dart';
class MyAppPage extends StatefulWidget {
const MyAppPage({super.key});
@override
State<MyAppPage> createState() => MyAppState();
}
class MyAppState extends State<MyAppPage> {
// 바텀 네비게이션 바 인덱스
int _selectedIndex = 0;
final List<Widget> _navIndex = [
MyMemoPage(),
CommunityPage(),
MyInfoPage(),
];
void _onNavTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CupertinoNavigationBar(
middle: const Text('Title'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
CupertinoButton(
padding: EdgeInsets.zero,
child: const Icon(
CupertinoIcons.arrowshape_turn_up_left,
size: 30,
),
onPressed: () {
showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: const Text('알림'),
content: const Text('로그아웃하시겠습니까?'),
actions: <CupertinoDialogAction>[
CupertinoDialogAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(context),
child: const Text('아니오'),
),
CupertinoDialogAction(
isDestructiveAction: true,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => LoginMainPage(),
),
);
},
child: Text('예'),
),
],
),
);
},
),
],
),
),
body: _navIndex.elementAt(_selectedIndex),
bottomNavigationBar: BottomNavigationBar(
fixedColor: Colors.blue,
unselectedItemColor: Colors.blueGrey,
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
items: const [
// BottomNavigationBarItem(
// icon: Icon(Icons.home_filled),
// label: '홈',
// backgroundColor: Colors.white,
// ),
BottomNavigationBarItem(
icon: Icon(Icons.my_library_books_outlined),
label: '메모',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.chat_bubble_2),
label: '커뮤니티',
),
BottomNavigationBarItem(
icon: Icon(Icons.person_outline),
label: '내 정보',
),
],
currentIndex: _selectedIndex,
onTap: _onNavTapped,
),
);
}
}
실행
계정 생성
- 생성할 계정 정보 입력
- 계정 생성 확인
- MySQL에서 계정이 생성되었는지 확인
- 비밀번호는 sha256 해시 알고리즘으로 암호화
select * from users;
로그인
- 생성한 계정 정보를 입력하여 로그인 시도
- 로그인 성공
로그아웃
- 앱 바의 우측 상단의 뒤로가기 버튼을 눌러 로그아웃 시도
- 로그아웃 "예"를 누른 화면
반응형