반응형
포스팅에서 진행한 환경
- OS : Windows 10
- IDE : Visual Studio Code
- Emulator : Pixel 5 API 27 (Android-x86 emulator), Windows (windows-x64)
이번 포스팅에서는 로그인과 로그아웃을 위해 아래와 같은 내용을 다룹니다.
- 공유 저장소(Shared Preferences)를 통해 유저의 정보가 있는지 확인하여 자동 로그인 기능 구현
- 공유 저장소에 대한 자세한 설명은 아래의 포스팅을 확인해주세요.
다른 설명이 필요하면 아래의 링크를 참고해주세요.
- 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' 폴더를 확인해주세요.
자동 로그인 기능 설계
시작 페이지 설정
앱을 실행하면 가장 먼저 자동 로그인을 설정하였는지 확인하는 페이지를 실행
- 메인 함수에서 자동 로그인 기능 여부를 확인할 페이지를 시작 페이지로 설정
- 자동 로그인 기능 여부 확인 페이지 이름 : TokenCheck
- TokenCheck 페이지 첫 호출 시 공유 저장소에서 자동 로그인 설정 정보를 확인(이하 토큰이라 칭함)
- 토큰이 있을 경우 : 메인 페이지로 바로 이동
- 토큰이 없을 경우 : 로그인 페이지로 이동
로그인 페이지 설정
자동 로그인 여부를 확인하는 토글 스위치의 값을 확인
- 토글 스위치의 값은 bool 타입이며 기본 값은 false로 설정
- 토글 스위치의 값을 저장할 변수는 'switchValue' 라는 변수로 정의
- true : 자동 로그인
- false : 자동 로그인하지 않음
자동 로그인 토글 스위치를 ON 으로 설정 할 경우
- 유저의 정보를 DB에서 확인하여 공유 저장소에 저장
- 공유 저장소에 저장은 '_setAutoLogin' 함수를 정의하여 token 이라는 키의 이름으로 저장
- 유저의 정보는 유니크한 값으로 저장하기 위해 DB의 users 테이블의 인덱스(PK)로 저장
자동 로그인 토글 스위치를 OFF 으로 설정 할 경우
- 로그인 시 공유 저장소에 저장되어 있는 유저 정보 삭제
- 공유 저장소의 토큰 키 삭제는 '_delAutoLogin' 함수를 정의하여 token 이라는 키의 이름 삭제
자동 로그인 기능 구현
- 앱 실행시 가장 먼저 보여줄 페이지 코드 추가
- loginMainPage.dart 파일에 토큰 확인(자동 로그인 여부 확인) 페이지 추가
// 자동 로그인 확인
// 토큰 있음 : 메인 페이지
// 토큰 없음 : 로그인 화면
class TokenCheck extends StatefulWidget {
const TokenCheck({super.key});
@override
State<TokenCheck> createState() => _TokenCheckState();
}
class _TokenCheckState extends State<TokenCheck> {
bool isToken = false;
@override
void initState() {
super.initState();
_autoLoginCheck();
}
// 자동 로그인 설정 시, 공유 저장소에 토큰 저장
void _autoLoginCheck() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
if (token != null) {
setState(() {
isToken = true;
});
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
// 토큰이 있으면 메인 페이지, 없으면 로그인 페이지
home: isToken ? MyAppPage() : LoginPage(),
);
}
}
- 로그인 페이지에 자동 로그인 여부를 설정하는 코드 추가
- loginMainPage.dart 파일에 LoginPage 코드 수정
- 자동 로그인 설정 함수 : _setAutoLogin()
- 자동 로그인 해제 함수 : _delAutoLogin()
- 로그인 시 자동 로그인 여부에 따라 조건 확인 코드 추가
// 자동 로그인 설정
void _setAutoLogin(String token) async {
// 공유저장소에 유저 DB의 인덱스 저장
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('token', token);
}
// 자동 로그인 해제
void _delAutoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove('token');
}
// 자동 로그인 확인
if (switchValue == true) {
_setAutoLogin(loginCheck!);
} else {
_delAutoLogin();
}
전체 소스 코드 보기 (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 TokenCheck extends StatefulWidget {
const TokenCheck({super.key});
@override
State<TokenCheck> createState() => _TokenCheckState();
}
class _TokenCheckState extends State<TokenCheck> {
bool isToken = false;
@override
void initState() {
super.initState();
_autoLoginCheck();
}
void _autoLoginCheck() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final String? token = prefs.getString('token');
if (token != null) {
setState(() {
isToken = true;
});
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: isToken ? MyAppPage() : LoginPage(),
);
}
}
// 로그인 페이지
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();
// 자동 로그인 설정
void _setAutoLogin(String token) async {
// 공유저장소에 유저 DB의 인덱스 저장
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('token', token);
}
// 자동 로그인 해제
void _delAutoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove('token');
}
@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('로그인 성공');
// 자동 로그인 확인
if (switchValue == true) {
_setAutoLogin(loginCheck!);
} else {
_delAutoLogin();
}
// 메인 페이지로 이동
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyAppPage(),
),
);
}
},
child: Text('로그인'),
),
),
),
],
),
),
),
),
);
}
}
메인 함수 시작 페이지 설정
- MaterialApp 의 home 의 속성을 위에서 정의한 TokenCheck() 페이지로 연결
import 'package:flutter/material.dart';
import 'package:flutter_memo_app/config/mySqlConnector.dart';
import 'package:flutter_memo_app/loginPage/loginMainPage.dart';
void main() {
dbConnector();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MemoApp',
home: TokenCheck(),
);
}
}
자동 로그인 확인
- 자동 로그인을 설정하지 않은 경우
- 앱을 재실행하면 로그인 페이지로 돌아감
- 자동 로그인을 설정한 경우
- 앱을 재실행해도 메인 페이지가 그대로 보여짐
반응형