[flutter] 플러터 RxDart에서의 로그인 및 회원가입 구현 방법

플러터(Flutter)와 RxDart를 사용하여 로그인 및 회원가입 기능을 구현하는 방법에 대해 알아보겠습니다.

1. RxDart란?

RxDart는 Dart언어를 위한 반응형 프로그래밍 라이브러리로써, 스트림(Streams)과 관찰자(Observer) 패턴을 사용하여 애플리케이션의 데이터 흐름을 조작하는 데 도움을 줍니다.

2. 플러터(Flutter)에서 RxDart 사용하기

RxDart를 플러터 프로젝트에 추가하려면 pubspec.yaml 파일에 다음 의존성을 추가해야 합니다:

dependencies:
  rxdart: ^0.26.0

의존성 추가 후, pub get 명령을 실행하여 의존성을 설치합니다.

3. 로그인 및 회원가입 스트림 생성하기

RxDart의 기능을 최대한 활용하기 위해 로그인 및 회원가입 기능을 스트림으로 구현해보겠습니다.

import 'package:rxdart/rxdart.dart';

class AuthBloc {
  final _emailController = BehaviorSubject<String>();
  final _passwordController = BehaviorSubject<String>();

  // 입력받은 이메일과 비밀번호를 검증하는 기능을 구현할 수 있습니다.
  Stream<bool> get isValid =>
      CombineLatestStream.combine2(email, password, (e, p) => true);

  // 이메일 입력을 받는 스트림
  Sink<String> get emailChanged => _emailController.sink;

  // 비밀번호 입력을 받는 스트림
  Sink<String> get passwordChanged => _passwordController.sink;

  // 검증된 이메일을 반환하는 스트림
  Stream<String> get email => _emailController.stream.transform(validateEmail);

  // 검증된 비밀번호를 반환하는 스트림
  Stream<String> get password =>
      _passwordController.stream.transform(validatePassword);

  // 이메일 대한 검증 로직
  final validateEmail = StreamTransformer<String, String>.fromHandlers(
    handleData: (email, sink) {
      if (email.contains('@')) {
        sink.add(email);
      } else {
        sink.addError('올바른 이메일 형식이 아닙니다');
      }
    },
  );

  // 비밀번호에 대한 검증 로직
  final validatePassword = StreamTransformer<String, String>.fromHandlers(
    handleData: (password, sink) {
      if (password.length >= 6) {
        sink.add(password);
      } else {
        sink.addError('비밀번호는 6자 이상이어야 합니다');
      }
    },
  );

  // 회원가입 기능 구현
  void signUp() {
    final email = _emailController.value;
    final password = _passwordController.value;
    
    // 회원가입 로직을 여기에 작성합니다.
  }

  // 로그인 기능 구현
  void signIn() {
    final email = _emailController.value;
    final password = _passwordController.value;
    
    // 로그인 로직을 여기에 작성합니다.
  }

  // AuthBloc 종료 시에 메모리 누수를 방지하기 위해 dispose 메소드를 추가합니다.
  void dispose() {
    _emailController.close();
    _passwordController.close();
  }
}

위 코드에서 AuthBloc 클래스는 이메일과 비밀번호의 변화를 관찰하고, 검증된 값을 내보내는 역할을 합니다. 또한 회원가입 및 로그인 버튼 클릭 시 해당 기능을 수행하는 메소드도 추가되어 있습니다.

4. 로그인 및 회원가입 UI 연동하기

위에서 생성한 AuthBloc 클래스를 사용하여 UI와 로그인 및 회원가입 기능을 연동해보겠습니다. 예시로 로그인 화면과 회원가입 화면을 작성해보겠습니다.

import 'package:flutter/material.dart';

class LoginScreen extends StatelessWidget {
  final _bloc = AuthBloc();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('로그인'),
      ),
      body: Container(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            StreamBuilder<String>(
              stream: _bloc.email,
              builder: (context, snapshot) {
                return TextField(
                  onChanged: _bloc.emailChanged.add,
                  decoration: InputDecoration(
                    labelText: '이메일',
                    errorText: snapshot.error,
                  ),
                );
              },
            ),
            StreamBuilder<String>(
              stream: _bloc.password,
              builder: (context, snapshot) {
                return TextField(
                  onChanged: _bloc.passwordChanged.add,
                  obscureText: true,
                  decoration: InputDecoration(
                    labelText: '비밀번호',
                    errorText: snapshot.error,
                  ),
                );
              },
            ),
            SizedBox(height: 16.0),
            StreamBuilder<bool>(
              stream: _bloc.isValid,
              builder: (context, snapshot) {
                return RaisedButton(
                  onPressed: snapshot.hasError || !snapshot.hasData
                      ? null
                      : _bloc.signIn,
                  child: Text('로그인'),
                );
              },
            ),
            SizedBox(height: 16.0),
            RaisedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => SignUpScreen()),
                );
              },
              child: Text('회원가입'),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _bloc.dispose();
    super.dispose();
  }
}

class SignUpScreen extends StatelessWidget {
  final _bloc = AuthBloc();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('회원가입'),
      ),
      body: Container(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            StreamBuilder<String>(
              stream: _bloc.email,
              builder: (context, snapshot) {
                return TextField(
                  onChanged: _bloc.emailChanged.add,
                  decoration: InputDecoration(
                    labelText: '이메일',
                    errorText: snapshot.error,
                  ),
                );
              },
            ),
            StreamBuilder<String>(
              stream: _bloc.password,
              builder: (context, snapshot) {
                return TextField(
                  onChanged: _bloc.passwordChanged.add,
                  obscureText: true,
                  decoration: InputDecoration(
                    labelText: '비밀번호',
                    errorText: snapshot.error,
                  ),
                );
              },
            ),
            SizedBox(height: 16.0),
            StreamBuilder<bool>(
              stream: _bloc.isValid,
              builder: (context, snapshot) {
                return RaisedButton(
                  onPressed: snapshot.hasError || !snapshot.hasData
                      ? null
                      : _bloc.signUp,
                  child: Text('회원가입'),
                );
              },
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _bloc.dispose();
    super.dispose();
  }
}

위 코드에서는 StreamBuilder 위젯을 사용하여 이메일과 비밀번호 입력값의 변화를 관찰하고, 에러 메시지를 출력하는 로직이 구현되어 있습니다. 또한 로그인 및 회원가입 버튼을 눌렀을 때 AuthBloc 클래스의 메소드가 실행되도록 설정되어 있습니다.

5. 결론

이제 플러터(Flutter)와 RxDart를 사용하여 로그인 및 회원가입 기능을 구현하는 방법을 알아보았습니다. RxDart를 사용하면 반응형 프로그래밍의 장점을 활용하여 애플리케이션의 데이터 흐름을 더욱 효율적으로 관리할 수 있습니다.