Flutter - 로그인, 로그아웃 구현하기 (2/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' 폴더를 확인해주세요.


회원가입(계정 생성) 기능 구현

  • 사용자가 입력한 아이디가 존재하는지 확인
    • 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;


로그인

  • 생성한 계정 정보를 입력하여 로그인 시도

 

  • 로그인 성공


로그아웃

  • 앱 바의 우측 상단의 뒤로가기 버튼을 눌러 로그아웃 시도

 

  • 로그아웃 "예"를 누른 화면

반응형