Flutter - 로그인, 로그아웃 구현하기 (3/3) - 자동 로그인 기능 구현

반응형

 

포스팅에서 진행한 환경

  • OS : Windows 10
  • IDE : Visual Studio Code
  • Emulator : Pixel 5 API 27 (Android-x86 emulator), Windows (windows-x64)

 

이번 포스팅에서는 로그인과 로그아웃을 위해 아래와 같은 내용을 다룹니다.

 

다른 설명이 필요하면 아래의 링크를 참고해주세요.


포스팅에서 다루는 예시 프로젝트는 아래의 깃허브 링크에서 다운로드 받을 수 있습니다.

'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(),
    );
  }
}

자동 로그인 확인

  • 자동 로그인을 설정하지 않은 경우
  • 앱을 재실행하면 로그인 페이지로 돌아감

 

  • 자동 로그인을 설정한 경우
  • 앱을 재실행해도 메인 페이지가 그대로 보여짐

 

반응형