[Flutter] MVVM 패턴과 상태 관리 4 : MVVM 구현 (GetX)

민태호·2024년 12월 12일
0

Flutter

목록 보기
12/23
post-thumbnail

MVVM 패턴과 상태 관리 3 : MVVM 구현 (Provider)
이전 게시물에 이어서 이번에는 GetX 상태관리 라이브러리를 사용하여 구현해보았다.


auth_service.dart

class AuthService {
  // 인증 번호 요청
  Future<void> requestPhoneVerification({required String phoneNumber}) async {
    return;
  }

  // 인증번호 인증
  Future<bool> verifySMSCode({required String smsCode}) async {
    return true;
  }

  // 로그인 상태 확인
  Future<bool> isLoggedIn() async {
    return true;
  }
}

코드 설명

Provider와 동일하다


login_view_model.dart

import 'package:flutter_velog/services/auth_service.dart';
import 'package:get/get.dart';

enum LoginViewState { idle, error, loading, smsCodeSent, loggedIn }

class LoginViewModel extends GetxController {
  final AuthService authService;

  LoginViewModel({required this.authService});

  // 상태 변수
  RxString phoneNumber = ''.obs;
  Rx<LoginViewState> state = LoginViewState.idle.obs;

  // 로그인 상태 점검
  Future<void> verifyLoginStatus() async {
    if (await authService.isLoggedIn()) {
      state.value = LoginViewState.loggedIn;
    }
  }

  Future<void> phoneNumberLogin() async {
    // 로딩
    state.value = LoginViewState.loading;

    // 인증요청 시도
    try {
      await authService.requestPhoneVerification(
          phoneNumber: phoneNumber.value);
      // 인증화면전환
      state.value = LoginViewState.smsCodeSent;
    } catch (e) {
      // 인증요청 에러
      state.value = LoginViewState.error;
    }
  }
}

코드 설명

class LoginViewModel extends GetxController { ... }

GetxController를 상속받아 구현된다.


// 변수 초기화
RxString phoneNumber = ''.obs;

// 변수 사용
phoneNumber.value = '01012345555';
Text(phoneNumber.value);
  • 변수 초기화 시 GetX 고유의 Rx타입 변수로 선언하고, 초기화 값에 .obs를 붙여 초기화 한다.
  • 변수 사용 시 변수 명에 .value를 붙여 값을 재할당 또는 사용한다.
  • Provider를 이용한 설계와 크게 다르지 않다.

sms_code_view_model.dart

import 'package:flutter_velog/services/auth_service.dart';
import 'package:get/get.dart';

enum SmsCodeViewState { idle, error, loading, verified }

class SmsCodeViewModel extends GetxController {
  final AuthService authService;

  SmsCodeViewModel({required this.authService});

  // 인증코드
  RxString smsCode = ''.obs;

  // 상태
  Rx<SmsCodeViewState> state = SmsCodeViewState.idle.obs;

  Future<void> verifySMSCode() async {
    // 로딩
    state.value = SmsCodeViewState.loading;

    // 인증 시도
    try {
      bool success = await authService.verifySMSCode(smsCode: smsCode.value);

      if (success) {
        // 성공 시 화면전환
        state.value = SmsCodeViewState.verified;
      } else {
        // 실패 시 에러
        state.value = SmsCodeViewState.error;
      }
    } catch (e) {
      // 인증 에러
      state.value = SmsCodeViewState.error;
    }
  }
}

코드 설명

LoginViewModel과 동일한 형식이다.


main.dart

import 'package:flutter/material.dart';
import 'package:flutter_velog/services/auth_service.dart';
import 'package:flutter_velog/viewmodels/login_view_model.dart';
import 'package:flutter_velog/views-getx/login_view.dart';
import 'package:get/get.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Flutter MVVM',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const LoginView(),
    );
  }
}

코드 설명


Widget build(BuildContext context) {
  return GetMaterialApp( ... );
}

기존의 MaterialAppGetMaterialApp으로 변경한다.


login_view.dart

import 'package:flutter/material.dart';
import 'package:flutter_velog/home_view.dart';
import 'package:flutter_velog/services/auth_service.dart';
import 'package:flutter_velog/viewmodels-getx/login_view_model.dart';
import 'package:flutter_velog/views-getx/sms_code_view.dart';
import 'package:get/get.dart';

class LoginView extends StatelessWidget {
  const LoginView({super.key});

  final String phoneNumberString = "휴대폰 번호";
  final String loginString = "로그인";
  final String errorMessage = "전화번호를 확인해주세요요";

  
  Widget build(BuildContext context) {
    final LoginViewModel loginViewModel =
        Get.put(LoginViewModel(authService: AuthService()));

    return Obx(() {
      WidgetsBinding.instance.addPostFrameCallback(
        (_) async {
          // 로그인 상태 검사
          await loginViewModel.verifyLoginStatus();

          if (loginViewModel.state.value == LoginViewState.loggedIn) {
            // 홈화면 이동
            Get.to(const HomeView());
          } else if (loginViewModel.state.value == LoginViewState.smsCodeSent) {
            // 인증번호 입력 이동
            Get.to(const SmsCodeView());
          }
        },
      );

      return Scaffold(
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            loginViewModel.state.value == LoginViewState.loading
                ? const CircularProgressIndicator()
                : TextField(
                    onChanged: (value) =>
                        loginViewModel.phoneNumber.value = value,
                    decoration: InputDecoration(
                        border: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(10)),
                        hintText: phoneNumberString),
                  ),
            const SizedBox(height: 10),
            if (loginViewModel.state.value == LoginViewState.error)
              Text(
                errorMessage,
                style: const TextStyle(color: Colors.red),
              ),
            const SizedBox(height: 10),
            SizedBox(
                width: double.maxFinite,
                child: FilledButton(
                    onPressed: () => loginViewModel.phoneNumberLogin(),
                    child: Text(loginString)))
          ],
        ),
      );
    });
  }
}

코드 설명

final LoginViewModel loginViewModel =
        Get.put(LoginViewModel(authService: AuthService()));

Get.put()을 이용하여 의존성을 주입할 수 있다.


return Obx(() {
	...
};

Obx()위젯을 이용하여 상태 변수 값이 변경되면 자동으로 감지하여 리빌드 하도록 할 수 있다.


sms_code_view.dart

import 'package:flutter/material.dart';
import 'package:flutter_velog/viewmodels-getx/login_view_model.dart';
import 'package:flutter_velog/viewmodels-getx/sms_code_view_model.dart';
import 'package:flutter_velog/home_view.dart';
import 'package:get/get.dart';

class SmsCodeView extends StatelessWidget {
  const SmsCodeView({super.key});

  final String verificationCodeString = "인증 번호";
  final String verificationString = "인증하기";
  final String errorMessage = "인증번호를 확인해주세요";

  
  Widget build(BuildContext context) {
    final LoginViewModel loginViewModel = Get.find<LoginViewModel>();
    final SmsCodeViewModel smsCodeViewModel =
        Get.put(SmsCodeViewModel(authService: loginViewModel.authService));

    return Obx(() {
      WidgetsBinding.instance.addPostFrameCallback((_) async {
        if (smsCodeViewModel.state.value == SmsCodeViewState.verified) {
          Get.to(const HomeView());
        }
      });

      return Scaffold(
        body: Padding(
          padding: const EdgeInsets.all(80),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              smsCodeViewModel.state.value == SmsCodeViewState.loading
                  ? const CircularProgressIndicator()
                  : TextField(
                      onChanged: (value) =>
                          smsCodeViewModel.smsCode.value = value,
                      decoration: InputDecoration(
                          border: OutlineInputBorder(
                              borderRadius: BorderRadius.circular(10)),
                          hintText: verificationCodeString),
                    ),
              const SizedBox(height: 10),
              if (smsCodeViewModel.state.value == SmsCodeViewState.error)
                Text(
                  errorMessage,
                  style: const TextStyle(color: Colors.red),
                ),
              const SizedBox(height: 10),
              SizedBox(
                  width: double.maxFinite,
                  child: FilledButton(
                      onPressed: () => smsCodeViewModel.verifySMSCode(),
                      child: Text(verificationString)))
            ],
          ),
        ),
      );
    });
  }
}

코드 설명

final LoginViewModel loginViewModel = Get.find<LoginViewModel>();

Get.find()를 이용하여 주입한 인스턴스를 불러와 사용할 수 있다.

profile
Flutter Developer

0개의 댓글