반응형
포스팅에서 진행한 환경
- OS : Windows 10
- IDE : Visual Studio Code
- Emulator : Pixel 5 API 27 (Android-x86 emulator), Windows (windows-x64)
이번 포스팅에서는 로그인과 로그아웃을 위해 아래와 같은 내용을 다룹니다.
- 유저의 아이디와 비밀번호를 users 테이블을 통해 유저를 식별하기 위한 MySQL DB 연동
- 회원가입(계정 생성), 로그인, 자동 로그인, 로그아웃을 위한 레이아웃 설계
다른 설명이 필요하면 아래의 링크를 참고해주세요.
- 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' 폴더를 확인해주세요.
기본 프로젝트 설정
- pubspec.yaml 파일에 아래의 코드 추가
dependencies:
flutter:
sdk: flutter
sqflite: ^2.2.8 # SQL 쿼리를 사용하기 위함
mysql_client: ^0.0.27 # MySQL DB 연동을 하기 위함
shared_preferences: ^2.1.0 # 공유저장소 사용을 하기 위함(자동 로그인)
crypto: ^3.0.3 # 정보 암호화를 위함 (사용자 비밀번호 암호화)
cupertino_icons: ^1.0.2 # iOS 스타일의 위젯 사용을 위함
MySQL DB 연동
아래의 포스팅에서 MySQL DB 연동에 대해 더 자세한 내용을 확인하실 수 있습니다.
MySQL 테이블 설계
참고 : 테스트를 위한 테이블이므로 최대한 간소화하여 테이블을 생성하였습니다.
MySQL 접속 설정
- 프로젝트 디렉토리 lib 폴더 내에 'config'라는 이름의 폴더 생성 (폴더명은 사용자에 따라 명명)
- MySQL 접속 설정의 정보를 저장하기 위한 dart 파일 생성 (mySQLConnector.dart)
- 코드 작성
- host, port, userName, password, databaseName은 자신의 DB 정보 입력
- 코드 간단 설명
- 반환타입을 MySQLConnection을 갖는, 즉 MySQL에 접속중인 상태 정보를 이용하여 다른 클래스에서 DB에 접근하기 위함
- 때문에 코드의 마지막에서 conn.close()를 사용하지 않았음
/* mySqlConnector.dart */
import 'package:mysql_client/mysql_client.dart';
import 'package:flutter_memo_app/config/dbInfo.dart';
// MySQL 접속
Future<MySQLConnection> dbConnector() async {
print("Connecting to mysql server...");
// MySQL 접속 설정
final conn = await MySQLConnection.createConnection(
host: DbInfo.hostName,
port: DbInfo.portNumber,
userName: DbInfo.userName,
password: DbInfo.password,
databaseName: DbInfo.dbName, // optional
);
await conn.connect();
print("Connected");
return conn;
}
접속 확인
- main.dart의 메인 함수에 정의한 MySQL 접속 함수 호출
import 'package:flutter/material.dart';
// MySQL 연동을 위한 다트 파일 호출
import 'package:flutter_memo_app/config/mySqlConnector.dart';
void main() {
dbConnector(); // mySQLConnector.dart의 dbConnector 함수 호출
}
- MySQL DB 접속 성공
레이아웃 설계
- 프로젝트 디렉토리 lib 폴더 내의 'loginPage' 라는 이름의 폴더 생성 (폴더명은 사용자에 따라 명명)
- 각각의 dart 파일 생성
- loginDB.dart : MySQL의 쿼리를 수행하기 위한 코드가 있는 파일
- 회원가입(계정 생성)
- 유저ID 중복확인
- 로그인
- loginMainPage.dart : 로그인을 하기 위한 페이지
- 추가 기능 : 자동 로그인 여부, 회원가입(계정 생성) 페이지로 이동
- memberRegisterPage.dart : 회원가입(계정 생성)을 하기 위한 페이지
- loginDB.dart : MySQL의 쿼리를 수행하기 위한 코드가 있는 파일
MySQL 쿼리 수행 파일(loginDB.dart)
아래의 포스팅에서 암호화에 대해 더 자세한 내용을 확인하실 수 있습니다.
코드 보기
더보기
import 'package:flutter_memo_app/config/hashPassword.dart';
import 'package:flutter_memo_app/config/mySqlConnector.dart';
import 'package:mysql_client/mysql_client.dart';
// 계정 생성
Future<void> insertMember(String userName, String password) async {
// MySQL 접속 설정
final conn = await dbConnector();
// 비밀번호 암호화
final hash = hashPassword(password);
// DB에 유저 정보 추가
try {
await conn.execute(
"INSERT INTO users (userName, password) VALUES (:userName, :password)",
{"userName": userName, "password": hash});
print(hash);
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
}
// 로그인
Future<String?> login(String userName, String password) async {
// MySQL 접속 설정
final conn = await dbConnector();
// 비밀번호 암호화
final hash = hashPassword(password);
// 쿼리 수행 결과 저장 변수
IResultSet? result;
// DB에 해당 유저의 아이디와 비밀번호를 확인하여 users 테이블에 있는지 확인
try {
result = await conn.execute(
"SELECT id FROM users WHERE userName = :userName and password = :password",
{"userName": userName, "password": hash});
if (result.isNotEmpty) {
for (final row in result.rows) {
print(row.assoc());
// 유저 정보가 존재하면 유저의 index 값 반환
return row.colAt(0);
}
}
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
// 예외처리용 에러코드 '-1' 반환
return '-1';
}
// 유저ID 중복확인
Future<String?> confirmIdCheck(String userName) async {
// MySQL 접속 설정
final conn = await dbConnector();
// 쿼리 수행 결과 저장 변수
IResultSet? result;
// ID 중복 확인
try {
// 아이디가 중복이면 1 값 반환, 중복이 아니면 0 값 반환
result = await conn.execute(
"SELECT IFNULL((SELECT userName FROM users WHERE userName=:userName), 0) as idCheck",
{"userName": userName});
if (result.isNotEmpty) {
for (final row in result.rows) {
return row.colAt(0);
}
}
} catch (e) {
print('Error : $e');
} finally {
await conn.close();
}
// 예외처리용 에러코드 '-1' 반환
return '-1';
}
로그인 페이지(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';
// 로그인 페이지
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: () {},
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: () {},
child: Text('계정 생성'),
),
),
],
),
),
],
),
),
),
),
);
}
}
반응형