[Flutter] 슈파베이스 연동하기 – ❸ 구현하기

이 글은 [Must Have] 코드팩토리의 플러터 프로그래밍 2판에서 발췌했습니다.
골든래빗 출판사
코드팩토리(최지호) 지음

슈파베이스(Supabase)는 모바일 및 웹 애플리케이션 개발 플랫폼을 빠르게 개발할 수 있는 백엔드 서비스입니다. 파이어베이스와 마찬가지로 백엔드를 직접 설계하지 않고 슈파베이스 SDK로 다양한 백엔드 기능을 사용할 수 있습니다. 이번 프로젝트는 슈파베이스와 플러터를 사용해서 일정 관리 앱을 구현해보도록 하겠습니다.

슈파베이스 연동하기는 총 3편입니다.

 

슈파베이스 연동하기 ❸ – 구현하기

 

4. 구현하기

4.1 로그인 화면 구현하기

 

[To Do]

1. 로그인 화면에서 구현할 버튼의 색상을 colors.dart 파일에 정의하겠습니다.

 

import 'package:flutter/material.dart';

const PRIMARY_COLOR = Color(0xFF0DB2B2);
const SECONDARY_COLOR = Color(0xFF335CB0);
final LIGHT_GREY_COLOR = Colors.grey[200]!;
final DARK_GREY_COLOR = Colors.grey[600]!;
final TEXT_FIELD_FILL_COLOR = Colors.grey[300]!;

 

2. lib/screen/auth_screen.dart 파일을 생성하고 로그인 화면을 제작해보겠습니다. Column을 이용해서 로고와 [구글로 로그인] 버튼을 중앙 정렬합니다.

 

import 'package:calendar_scheduler/const/colors.dart';
import 'package:calendar_scheduler/screen/home_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';

class AuthScreen extends StatelessWidget {
  const AuthScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: EdgeInsets.symmetric(horizontal: 16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Center(
              child: FractionallySizedBox(
                widthFactor: 0.7,
                child: Image.asset(
                  'assets/img/logo.png',
                ),
              ),
            ),
            SizedBox(height: 16.0),
            ElevatedButton(
              onPressed: () => onGoogleLoginPress(context),
              style: ElevatedButton.styleFrom(
                backgroundColor: SECONDARY_COLOR,
              ),
              child: Text('구글로 로그인'),
            ),
          ],
        ),
      ),
    );
  }

  onGoogleLoginPress(BuildContext context) async {
    GoogleSignIn googleSignIn = GoogleSignIn(
      scopes: [
        'email',
      ],
    );

    try {
      GoogleSignInAccount? account = await googleSignIn.signIn();

      final GoogleSignInAuthentication? googleAuth = await account?.authentication;

      final credential = GoogleAuthProvider.credential(
        accessToken: googleAuth?.accessToken,
        idToken: googleAuth?.idToken,
      );

      final result = await FirebaseAuth.instance.signInWithCredential(credential);

      Navigator.of(context).push(
        MaterialPageRoute(
          builder: (_) => HomeScreen(),
        ),
      );
    } catch (error) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('로그인 실패')),
      );
    }
  }
}

 

3. main.dart 파일에서 초기 화면을 HomeScreen이 아닌 AuthScreen으로 변경해줍니다.

 

import 'package:calendar_scheduler/screen/auth_screen.dart';
import 'package:calendar_scheduler/screen/home_screen.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:calendar_scheduler/database/drift_database.dart';
import 'package:get_it/get_it.dart';
import 'package:calendar_scheduler/provider/schedule_provider.dart';
import 'package:calendar_scheduler/repository/schedule_repository.dart';
import 'package:provider/provider.dart';
import 'package:calendar_scheduler/firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  await initializeDateFormatting();

  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      home: AuthScreen(),
    ),
  );
}

 

4. 프로젝트를 실행하면 [구글로 로그인] 버튼이 생성된 로그인 스크린을 확인할 수 있습니다.

 

4.2 슈파베이스 세팅하기

슈파베이스를 프로젝트에서 사용하려면 프로젝트에 Project URL과 API Key를 등록해줘야 합니다.

 

[To Do]

1. lib/main.dart 파일에 슈파베이스를 초기화해주겠습니다. url 파라미터에 2.2 ‘슈파베이스 회원가입’에서 복사해두었던 Project URL 값을 입력해주고 anonKey 파라미터에는 API Key 값을 입력해줍니다.

 

💡Note: 혹시 대시보드에서 Project URL과 anonKey가 보이지 않으면 [Settings] > [API] 화면을 참고하세요.

 

...생략...
import 'package:supabase_flutter/supabase_flutter.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  await Supabase.initialize(
    url: '{Project URL 입력}',
    anonKey:'{API Key 입력}',
  );

  await initializeDateFormatting();

  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      home: AuthScreen(),
    ),
  );
}

 

4.3 슈파베이스 인증 세팅하기

파이어베이스에서도 그러하듯 슈파베이스에서도 인증은 매우 중요한 기능 중 하나입니다. 슈파베이스에서 구글 로그인을 진행하는 방법을 알아보겠습니다.

 

[To Do]

1. lib/auth_screen.dart 파일에는 이미 [구글로 로그인] 버튼이 구현되어 있고 버튼을 누르면 실행되는 onGoogleLoginPress( ) 함수가 정의돼 있습니다. 현재 이 함수는 파이어베이스를 사용하여 구글 로그인을 진행하는 방식으로 정의되어 있습니다. 그러므로 슈파베이스로 로그인하는 방식으로 함수를 변경해줘야 합니다. 파이어베이스의 signInWithCredential( ) 함수 대신 슈파베이스의 signInWithIdToken( ) 함수를 실행해서 로그인해보겠습니다.

 

import 'package:supabase_flutter/supabase_flutter.dart';
...생략...

class AuthScreen extends StatefulWidget {
  ...생략...

  onGoogleLoginPress(BuildContext context) async {
    GoogleSignIn googleSignIn = GoogleSignIn(
      scopes: [
        'email',
      ],
      clientId: '{iOS Client ID 입력}',
      serverClientId: '{Web Client ID 입력}',
    );

    try {
      GoogleSignInAccount? account = await googleSignIn.signIn();

      final GoogleSignInAuthentication? googleAuth = await account?.authentication;

      // googleAuth 객체가 null이거나 idToken이 null이거나 accessToken이 null이면
      // 인증이 정상적으로 진행된 상태가 아니기 때문에 에러를 던져줍니다.
      if (googleAuth == null || googleAuth.idToken == null || googleAuth.accessToken == null) {
        throw Exception('로그인 실패');
      }

      // ❶ 슈파베이스로 소셜 로그인을 진행하는 방법입니다.
      await Supabase.instance.client.auth.signInWithIdToken(
        provider: Provider.google,
        idToken: googleAuth.idToken!,
        accessToken: googleAuth.accessToken!,
      );

      Navigator.of(context).push(
        MaterialPageRoute(
          builder: (_) => HomeScreen(),
        ),
      );
    } catch (error) {
      print(error);
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('로그인 실패')),
      );
    }
  }
}

 

2. 프로젝트를 실행하고 [구글로 로그인] 버튼을 눌러서 로그인을 실행해봅니다. 로그인이 성공적으로 진행되며 다음 화면으로 넘어가는 것을 확인할 수 있습니다. 이때 iOS로 구글 로그인을 사용하려면 22장의 22.2.8을 진행해야 하므로 이전에 실습한 <[MustHave 코드팩토리의 플러터 프로그래밍(2판)> 22장의 ios/GoogleService-Info.plist에서 REVERSED_CLIEND_ID 아래에 있는 <string>…</string>을 복사해서 23장 템플릿 프로젝트의 ios/GoogleService-Info.plist와 ios/Info.plist에 붙여 넣으세요.

 

 

💡Note: iOS 시뮬레이터에서 Unable to boot simulator 오류가 발생하면 [시스템 설정] > [저장 공간]에 있는 ‘개발자’ 항목의 ‘xcode 프로젝트 빌드 파일 > xcode 캐시’를 삭제하고 실행하면 됩니다.

 

3. 로그인이 성공적으로 진행되면서 사용자 생성이 잘 되었음을 슈파베이스 대시보드에서 확인해보겠습니다. 슈파베이스 대시보드로 이동한 후 [Table] > [auth] > [users]를 클릭한 후 로그인한 이메일로 사용자가 하나 생성된 걸 확인해봅니다.

 

 

4.4 일정 테이블 생성하기

파이어스토어와 다르게 슈파베이스는 SQL 데이터베이스를 사용하기 때문에 테이블 구조를 생성한 다음 데이터를 생성할 수 있습니다. 먼저 슈파베이스 대시보드에서 테이블을 생성해보겠습니다.

 

[To Do]

1. 슈파베이스 대시보드로 이동한 다음 [public] 스키마를 선택합니다. 그리고 중앙의 [Create a new table] 버튼을 눌러서 테이블 생성 창을 실행합니다. public 스키마는 외부에 공개되는 기본 스키마로 슈파베이스 SDK를 이용해서 접근 가능한 테이블을 생성하는 위치입니다.

 

 

2. Name 필드에는 테이블 이름을 지정하고 Description 필드는 테이블에 대한 설명을 입력하는 필드입니다. Name은 ‘schedule’을 넣어주고 Description은 ‘일정을 저장하는 테이블’로 입력하겠습니다. 추가로 [Enable Row Level Security (RLS)] 체크 버튼을 눌러서 RLS를 활성화하겠습니다. ‘행 수준 보안’에서 설명했듯 RLS는 postgresql에서 테이블과 Row의 접근을 엄격히 제어할 수 있는 강력한 보안 기능입니다. 테이블 설정을 완료한 다음 RLS 설정에 대해서도 자세히 알아보겠습니다.

 

 

3. 다음은 테이블의 컬럼을 생성해야 합니다. 기본으로 각 행의 특수값인 id 컬럼과 행이 생성된 시간을 저장하는 created_at 컬럼이 있습니다. 여기에 일정의 날짜인 date를 글자 타입인 text 타입으로, 일정 내용인 content를 text 타입으로, 시작 시간인 start_time을 2바이트 크기의 정수인 int2 타입으로, 종료 시간인 end_time을 int2 타입으로 추가해보겠습니다. 각 컬럼의 이름 아래쪽의 [Add column] 버튼을 누르면 컬럼 추가가 가능합니다.

 

 

4. 다음은 컬럼의 제한 사항을 변경해보겠습니다. 각 컬럼 오른쪽의 톱니바퀴 세팅 버튼을 누르면 제한 사항을 변경할 수 있습니다. content, start_time, end_time 컬럼의 Is Nullable 제한 사항을 모두 비활성화하겠습니다. Nullable은 Null값을 허용하는 조건으로, Is Nullable을 비활성화하면 값이 null이 될 수 없으며 꼭 값이 입력돼야 합니다.

 

 

5. 마지막으로 어떤 사용자가 생성한 일정인지 알 수 있는 author 컬럼을 추가해보겠습니다. [Add column] 버튼을 누른 후 컬럼 이름을 author로 설정합니다. 그리고 타입을 UUID로 선택한 다음 is Nullable 체크를 해제합니다.

 

💡Note: UUID는 다섯 개의 값이 ‘-’로 이어져 있는 형태를 띄고 있는 특수 문자열값으로 언제 생성해도 절대로 겹치지 않는 특성을 가집니다.

 

 

6. 생성된 일정을 실제 사용자와 연동하기 위해 쇠고리 모양의 버튼을 눌러서 연동을 진행해보겠습니다.

 

 

7. Foreign Key Relation 창이 실행되면 auth 스키마를 선택하고 users 테이블을 선택합니다. 그리고 users 테이블의 id 컬럼을 선택해서 schedule 테이블의 author 컬럼과 연동합니다. 나머지 옵션은 그대로 두고 [Save] 버튼을 눌러서 저장합니다.

 

 

8. 쇠고리 버튼의 색이 변경되면 연동 성공입니다. 앞으로 schedule 테이블에 데이터를 생성할 때는 author 컬럼을 필수로 입력해줘야 합니다. author 컬럼값은 users 테이블의 id 컬럼값과 연동되었으므로 꼭 users 테이블의 id 컬럼에 있는 값이어야 합니다.

 

 

9. 이어서 id 컬럼의 타입을 int8에서 UUID로 변경하겠습니다. int8로 설정할 경우 1부터 오름차순으로 숫자가 하나씩 배정됩니다. UUID를 선택할 경우 파이어스토어에서 사용했던 것과 같이 임의의 문자열값이 ID값으로 배정됩니다. 22.4.4 ‘파이어베이스 인증 기반으로 일정 CRUD 기능 변경하기’에서 파이어스토어로 구현한 프로젝트의 ID에 UUID 타입을 사용했기 때문에 슈파베이스에서도 똑같이 UUID 타입을 사용하겠습니다.

 

 

10. 끝으로 author의 기본값을 지정하는 Default Value 필드에 auth.uid(   )를 입력해줍니다. auth.uid(   )는 슈파베이스에서 사용 가능한 특수한 값으로 사용자 고유의 id값을 의미합니다. 다시 말해 auth.uid(   )는 현재 로그인되어 있는 사용자의 ID를 가져옵니다. 데이터를 입력하는 insert 요청이 실행될 때마다 현재 로그인되어 있는 사용자 ID를 자동으로 author 컬럼에 입력합니다.

 

 

11. 최종적으로 [Save] 버튼을 눌러서 테이블 생성을 완료합니다.

 

 

4.5 일정 테이블 RLS 설정하기

테이블에 RLS 기능을 활성화시키면 RLS Policy로 권한을 허가해주기 전까지 어떤 데이터도 접근할 수 없는 것이 기본 설정입니다. RLS Policy를 추가해서 일정 생성자만 일정 데이터에 접근할 수 있도록 권한 설정을 진행해보겠습니다.

 

[To Do]

1. 슈파베이스 대시보드에서 [Authentication] 버튼을 누른 다음 [Policies] 버튼을 누릅니다. 오른쪽에 보이는 schedule 테이블의 [New Policy] 버튼을 눌러서 schedule 테이블과 관련된 RLS Policy를 생성하는 창을 실행합니다.

 

 

2. 기본 템플릿을 제공해주는 [Get started quickly] 버튼과 전체 맞춤 설정이 가능한 [For full customization] 버튼이 있습니다. 저희는 직접 전체 맞춤 설정하는 방법을 학습해보기 위 해 [For full customization] 버튼을 누르겠습니다.

 

 

3. 가장 먼저 Policy name에 이름을 정해 입력하고 Allowed operation에서 [All]을 선택하여 모든 CRUD 기능에 Policy를 적용합니다. Target roles은 아무것도 선택하지 않고 기본값인 Defaults(public)가 지정되도록 합니다.

 

 

4. 이제 조건문을 작성할 차례입니다. Allowed Operation에서 All을 선택했기 때문에 USING 조건과 WITH CHECK 조건을 모두 작성할 수 있습니다. 두 조건 모두 코드 ‘auth.uid(   ) = author’를 입력해서 모든 CRUD 작업에서 현재 로그인한 사용자가 생성한 일정만 조회 및 업데이트가 가능하도록 하겠습니다. 코드 입력을 완료했으면 [Review] 버튼을 눌러줍니다.

 

 

5. 입력한 값들을 기반으로 생성된 쿼리문을 확인한 후 [Save policy] 버튼을 클릭합니다.

 

 

6. schedule 테이블에 “Enable all for schedule creator” Policy가 생성된 걸 확인합니다.

 

 

4.6 일정 CRUD 로직 작성하기

슈파베이스로 구현한 인증 기능을 기반으로 일정 관리 로직을 변경해보겠습니다.

 

[To Do]

1. 먼저 일정을 생성하는 플러터 코드를 작성해보겠습니다. 일정 생성 로직은 lib/component/schedule_bottom_sheet.dart 파일의 onSavePressed(   ) 함수에 정의되어 있습니다. 파이어 스토어에 일정을 추가하는 로직이 작성되어 있던 이 부분을 삭제하고 슈파베이스 데이터베이스에 일정을 삽입하는 코드를 추가하겠습니다.

 

...생략...
import 'package:supabase_flutter/supabase_flutter.dart';

class ScheduleBottomSheet extends StatefulWidget {
  ...생략...

  void onSavePressed(BuildContext context) async {
    if (formKey.currentState!.validate()) {
      formKey.currentState!.save();

      final schedule = ScheduleModel(
        id: Uuid().v4(),
        content: content!,
        date: widget.selectedDate,
        startTime: startTime!,
        endTime: endTime!,
      );

      // Supabase 인스턴스 불러오기
      final supabase = Supabase.instance.client;

      // ❶ Supabase schedule 테이블에 데이터 삽입
      await supabase.from('schedule').insert(
        schedule.toJson(),
      );

      Navigator.of(context).pop();
    }
  }

 

❶ 슈파베이스에 INSERT문을 실행하기 위해서는 먼저 from( ) 함수에 대상 테이블 이름을 입력해줍니다. 다음으로 insert( ) 함수에 생성할 데이터 정보를 Map 형태로 입력합니다. 만약 insert( ) 함수에 select( ) 함수를 이어서 실행하면 저장된 값을 반환받을 수도 있습니다.

 

2. 다트 언어에서는 키값을 작성할 때 맨 첫 글자를 제외한 나머지 단어의 첫 번째 알파벳을 대문자로 작성하는 카멜 표기법를 사용합니다. 그러나 PostgreSQL 컬럼은 단어 사이를 ‘_’로 구분하는 게 일반적입니다. 그래서 ScheduleModel의 toJson(   ) 함수를 변형해서 startTime과 endTime으로 변환되던 키값들을 start_time과 end_time으로 변경하겠습니다.

 

class ScheduleModel {
  ...생략...

  ScheduleModel.fromJson({ // ➊ JSON으로부터 모델을 만들어내는 생성자
    required Map<String, dynamic> json,
  })  : id = json['id'],
        content = json['content'],
        date = DateTime.parse(json['date']),
        startTime = json['start_time'],
        endTime = json['end_time'];

  Map<String, dynamic> toJson() {  // ➋ 모델을 다시 JSON으로 변환하는 함수
    return {
      'id': id,
      'content': content,
      'date':
      '${date.year}${date.month.toString().padLeft(2, '0')}${date.day.toString().padLeft(2, '0')}',
      'start_time': startTime,
      'end_time': endTime,
    };
  }
}

 

3. 코드를 수정한 다음에는 앱을 실행하고 달력에서 2023년 11월 7일을 선택한 후 12시부터 14시까지 진행되는 “인프런에서 코드팩토리의 NestJS 강의 듣기” 일정을 생성해보겠습니다. 생성 후에는 아마 불러오기가 안 될 것입니다. 바로 다음에 일정 불러오기를 구현합니다.

 

 

4. 일정 정보를 불러오는 작업을 하겠습니다. 파이어스토어는 자동으로 데이터를 실시간으로 저장 및 동기화하는 Real Time 쿼리가 활성화되기 때문에 수시로 변하는 데이터에 적합한 StreamBuilder 위젯을 사용했습니다. 슈파베이스 프로젝트는 리얼 타임real time을 활성화하지 않았기 때문에 일회성 응답에 적합한 FutureBuilder 위젯을 사용해서 일정 정보를 가져와보겠습니다. 만약 슈파베이스에서 리얼 타임을 활성화했다면 마찬가지로 StreamBuilder를 이용해서 지속적으로 일정 정보 업데이트를 받아올 수 있습니다.

 

...생략...
import 'package:supabase_flutter/supabase_flutter.dart';

class _HomeScreenState extends State<HomeScreen> {
...생략...

  @override
  Widget build(BuildContext context) {
    // ➊ 선택된 날짜를 관리할 변수
    final future = Supabase.instance.client.from('schedule').select<List<Map<String, dynamic>>>().eq('date',
        '${selectedDate.year}${selectedDate.month.toString().padLeft(2, '0')}${selectedDate.day.toString().padLeft(2, '0')}');

    return Scaffold(
      floatingActionButton: FloatingActionButton(
        backgroundColor: PRIMARY_COLOR,
        onPressed: () async {
          await showModalBottomSheet(
            context: context,
            isDismissible: true,
            isScrollControlled: true,
            builder: (_) => ScheduleBottomSheet(
              selectedDate: selectedDate,
            ),
          );
          // 새로운 일정 생성이 완료되면 setState() 함수를 실행해서 build() 함수를
          // 재실행합니다.
          setState(() {});
        },
        child: Icon(
          Icons.add,
        ),
      ),
      body: SafeArea(
        child: Column(
          children: [
            ...생략...
            // StreamBuilder를 FutureBuilder로 변환
            FutureBuilder<List<Map<String, dynamic>>>(
              future: future,
              builder: (context, snapshot) {
                return TodayBanner(
                  selectedDate: selectedDate,

                  count: snapshot.data?.length ?? 0,
                );
              },
            ),
            SizedBox(height: 8.0),
            Expanded(
              // StreamBuilder를 FutureBuilder로 변환
              child: FutureBuilder<List<Map<String, dynamic>>>(
                future: future,
                builder: (context, snapshot) {
                  if (snapshot.hasError) {
                    return Center(
                      child: Text('일정 정보를 가져오지 못했습니다.'),
                    );
                  }

                  if (snapshot.connectionState == ConnectionState.waiting || !snapshot.hasData) {
                    return Container();
                  }

                  // 반환받은 List<Map<String, dynamic>> 데이터를
                  // List<ScheduleModel>로 변환
                  final schedules = snapshot.data!
                      .map(
                        (e) => ScheduleModel.fromJson(json: e),
                      )
                      .toList();

                  return ListView.builder(...생략...);
                },
              ),
            ),
          ],
        ),
      ),
    );
  }

 

5. 저장 후 2023년 11월 7일을 눌러보면 기존에 생성했던 “인프런에서 코드팩토리의 NestJS 강의 듣기” 일정을 조회할 수 있습니다.

 

 

6. 일정 조회와 생성 기능을 구현했으니 이번에는 삭제인 DELETE 기능을 구현해보겠습니다. Dismissible 위젯의 onDismissed(   ) 함수에는 파이어베이스 파이어스토어의 delete(   ) 함수를 이용한 삭제 기능이 구현되어 있습니다. 이 부분을 슈파베이스의 delete(   ) 함수로 수정해서 일정 을 삭제할 수 있도록 바꿔보겠습니다.

 

...생략...

class _HomeScreenState extends State<HomeScreen> {
  ...생략...

  @override
  Widget build(BuildContext context) {
    ...생략...

    return Scaffold(
      floatingActionButton: ...생략...(,
      body: SafeArea(
        child: Column(
          children: [
            ...생략...
            Expanded(
              child: FutureBuilder<List<Map<String, dynamic>>>(
                future: future,
                builder: (context, snapshot) {
                  ...생략...

                  return ListView.builder(
                    itemCount: schedules.length,
                    itemBuilder: (context, index) {
                      final schedule = schedules[index];

                      return Dismissible(
                        key: ObjectKey(schedule.id),
                        direction: DismissDirection.startToEnd,
                        onDismissed: (DismissDirection direction) async{
                          // delete() 함수를 실행할 때 match() 함수에 삭제할 값의
                          // 조건을 입력하면 됩니다.
                          await Supabase.instance.client.from('schedule').delete().match({
                            'id': schedule.id,
                          });

                          // 삭제 결과를 즉각 반영하기 위해 build() 함수를 실행합니다.
                          setState(() {});
                        },
                        child: ...생략...
                      );
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

 

7. 생성했던 일정을 우로 밀어서 삭제해보겠습니다. 정상적으로 삭제가 진행되면 일정 삭제 기능 구현 완료입니다.

 

 

4.7 로그아웃 기능 구현하기

로그아웃 기능은 <[MustHave 코드팩토리의 플러터 프로그래밍(2판)> 22장 ‘로그아웃 기능 구현하기’에서 진행했던 형태와 동일합니다. 다만 [로그아웃] 버튼을 눌렀을 때 실행할 기능만 슈파베이스 기반의 기능으로 변경해보겠습니다.

 

[To Do]

1. TodayBanner의 오른쪽 끝에 로그아웃 아이콘을 생성하고 슈파베이스 로그아웃 기능을 구현해보겠습니다.

 

import 'package:supabase_flutter/supabase_flutter.dart';
...생략...
class TodayBanner extends StatelessWidget {
  ...생략...
  @override
  Widget build(BuildContext context) {
    ...생략...
    return Container(
      color: PRIMARY_COLOR,
      child: Padding(
        padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Expanded(
              child: Text(
                '${selectedDate.year}년 ${selectedDate.month}월 ${selectedDate.day}일', // “년 월 일” 형태로 표시
                style: textStyle,
              ),
            ),
            Text(
              '$count개', // 일정 개수 표시
              style: textStyle,
            ),
            const SizedBox(width: 8.0),
            Row(
              children: [
                GestureDetector(
                  onTap: () async {
                    
                    // 슈파베이스의 signOut() 함수를 실행하면 로그아웃할 수 있습니다.
                    await Supabase.instance.client.auth.signOut();

                    Navigator.of(context).pop();
                  },
                  child: Icon(
                    Icons.logout,
                    color: Colors.white,
                    size: 16.0,
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

 

5. 테스트하기

❶ 안드로이드 스튜디오에서 [Run] 버튼을 눌러서 시뮬레이터, 에뮬레이터 또는 본인 기기에서 앱을 실행해보세요.

❷ [구글로 로그인] 버튼을 눌러서 첫 번째 계정(이하 A 계정)으로 로그인합니다.

❸ 2023년 10월 31일을 선택한 후 일정 생성 [+] 버튼을 눌러서 12시부터 14시까지 “코드팩토리의 NestJS 강의 공부” 일정을 생성합니다.

 

 

❹ 일정이 정상적으로 생성된 걸 확인합니다.

❺ 로그아웃 후 새로운 계정(이하 B계정)으로 로그인합니다.

❻ B 계정으로 로그인한 상태에서 2023년 10월 31일을 선택하면 A 계정의 “코드팩토리의 NestJS 강의 공부” 일정이 존재하지 않는 것을 확인합니다.

 

 

❼ B 계정에 14시부터 16시까지 “코드팩토리의 Typescript 프로그래밍 공부하기” 일정을 생성합니다.

❽ 로그아웃 후 A 계정으로 로그인한 다음 2023년 10월 31일을 확인하면 “코드팩토리의 NestJS 강의 공부” 일정만 존재하는 것을 확인할 수 있습니다. 즉, 각 계정별로 본인이 생성한 일정만 조회가 가능합니다.

 

 

이 테스트 과정에서 RLS의 사용자 기반 규칙 활용 기능이 얼마나 강력한지 확인할 수 있습니다. 슈파베이스 쿼리에서는 author 컬럼과 관련된 필드를 전혀 필터링하고 있지 않지만 RLS PostgreSQL Policy 문법 설정으로 자동으로 현재 로그인한 사용자가 생성한 일정들만 불러옵니다.

 

슈파베이스 마무리

슈파베이스를 사용한 인증 및 CRUD 기능 구현 방법에 대해 알아봤습니다. 슈파베이스는 파이어베이스와 제공해주는 서비스가 매우 비슷하지만 PostgreSQL 데이터베이스를 사용하기 때문에 조금 더 구조화된 데이터를 생성할 수 있었습니다. 올인원 백엔드 솔루션을 사용하고 싶은데 NoSQL을 사용하기 싫다면 슈파베이스와 같은 서비스도 존재하는 것을 기억하길 바랍니다.

 

핵심 요약

  1. 슈파베이스는 파이어베이스와 비슷한 올인원 백엔드 솔루션입니다.
  2. NoSQL 데이터베이스를 사용하는 파이어스토어와 다르게 슈파베이스는 SQL 데이터베이스인 PostgreSQL을 사용합니다.
  3. 슈파베이스 인증 또한 파이어베이스 인증처럼 다양한 소셜 로그인을 지원합니다.
  4. 슈파베이스는 SQL 데이터베이스를 사용하기 때문에 테이블을 생성하는 과정이 필수입니다.
  5. RLS 설정을 통해 슈파베이스 데이터베이스에 접근할 수 있는 권한을 손쉽게 제어할 수 있습니다.

 

업그레이드 아이디어

  1. 로그인 방법 중 이메일 로그인을 추가해보세요. 이메일 로그인은 기본으로 활성화되어 있으며 다음 함수를 이용해서 회원가입 및 로그인을 진행할 수 있습니다.
    1. 회원가입: Supabase.instance.client.auth.signUp()
    2. 로그인: Supabase.instance.client.auth.signInWithPassword()
  2. 슈파베이스 Real Time 기능을 사용하여 파이어스토어처럼 실시간으로 데이터 업데이트를 반영할 수 있는 기능을 구현해보세요.💡Hint: 테이블 설정에서 Real Time 기능을 활성화시키고 StreamBuilder를 사용하여 슈파베이스에서 Stream 을 받아오면 됩니다.
  3. 현재 로그인한 사용자가 생성한 일정이 아닌 다른 사람의 일정은 조회되지 않도록 RLS 설정 이 되어 있습니다. 로그인한 사용자라면 누구든 저장된 모든 일정을 조회할 수 있도록 RLS 를 변경해봅시다. 또한 다트 코드에서 슈파베이스에 조회 요청을 보낼 때 사용자가 생성한 정보만 가져오도록 코드를 변경해보세요. 💡Hint: select() 함수 실행 후 eq() 함수를 실행하면 SELECT문의 조건을 설정할 수 있습니다. 첫 번째 파라미터에 컬럼명을 입력하고 두 번째 파라미터에 값을 입력합니다. Supabase.instance.client.auth.currentUser.d를 실행하면 현재 로그인한 사용자의 ID를 가져올 수 있습니다.

 

지금까지 총 3편에 걸쳐서 슈파베이스에 대해 알아보고, 플러터를 사용해서 일정 관리 앱을 구현해보았습니다. 다양한 프로젝트에 활용할 수 있는 만큼 슈파베이스 사용법을 익혀두면 많은 도움이 되리라 생각합니다.

최지호(코드팩토리) 
임페리얼 칼리지 런던을 졸업하고 계리 컨설팅 회사 밀리만(Milliman) 한국 지사에서 소프트웨어 엔지니어로 일했습니다. 현재 주식회사 코드팩토리를 창업하여 개발을 하면서 초보자뿐만 아니라 현직 개발자에게도 유용한 개발 강의를 제작합니다. 밀리의서재 플러터 전환 차세대 프로젝트를 리드했습니다.

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내