[Flutter] GetX로 상태관리 🪄

dosilv·2021년 12월 22일
2

Provider를 사용해서 첫 프로젝트를 잘 마쳤지만! 별 문제는 없었지만! 그럼에도 불구하고 GetX를 써본 사람들이 극찬하는 이유가 있겠지..? 하고 호기심이 생겼다. 다음번 앱은 GetX로 만들고 싶어서 공부 중에 하는 정리 📚



🪄 Simple 방식

기존 Provider 사용법과 거의 유사한 방법...! ChangeNotifier의 역할을 GetxController가, Consumer의 역할을 GetBuilder가 거의 동일하게 구현함!

대신 사용할 때 context가 필요하지 않아서 stateless widget 안에서도 따로 위로 빼서 사용할 수 있다. 그리고 아래에 있는 Reactive 방식과 비교하면 제공되는 기능은 단순하지만, 리소스가 적다!

예제로 컨트롤러를 이용해서 토끼한테 당근을 줬다 뺏는.. 쓸데없는 화면을 구현해보았다.

토끼랑 당근 사이에 SizedBox를 놓고 width를 state값으로 만든 다음에, getX로 관리하는 게 끝..! 만약 토끼와 당근 사이의 거리가 0이면 거리를 그만 줄이고, 하트가 나타났다 사라지도록 했다.


🎩 Controller 부분

GetxController를 상속한 클래스를 만들고, 필요한 변수와 메서드를 작성해준다.

import 'package:get/get.dart';

class DistanceController extends GetxController {
  double _distance = 100;
  double get distance => _distance;
  
  bool _heart = false;
  bool get heart => _heart;

  void getClose() {
    if (_distance == 0) {
      _heart = true;
      update();
      Future.delayed(const Duration(milliseconds: 500), () {
        _heart = false;
        update();
      });
      return;
    }
    //⬆️ 거리가 0이면 distance를 감소시키는 대신 heart를 true로 만들고, 0.5초 후에 다시 false가 되도록 하는 코드
    _distance -= 20;
    update();
  }

  void getFar() {
    _distance += 20;
    update();
  }
}

state의 상태변화를 알리기 위해 update()를 사용한다. (privider의 notifierListener와 같은 역할!)


🎩 Controller 선언하기

여러 위젯에서 쓰이는 컨트롤러의 경우 상위 위젯에서 put으로 먼저 등록하고,

Get.put(DistanceController());

사용되는 위젯에서 find로 변수에 담아준 뒤 쓰면 된다.

final controller = Get.find<DistanceController>();

만약 하나의 위젯에서만 사용된다면 그냥 해당 위젯 내부에 put으로 바로 선언해서 써도 문제 없었다!

final controller = Get.put(DistanceController());

provider의 ChangeNotifierProvider 세팅에 비하면 아 쥬 간 단~~~!


🎩 Controller에서 정의한 변수 이용하기

Stack(
  alignment: Alignment.topCenter,
  children: <Widget>[
    //⬇️ heart 사용 부분
    GetBuilder<DistanceController>(
        builder: (_) => AnimatedOpacity(
            duration: const Duration(milliseconds: 500),
            opacity: controller.heart ? 1 : 0,
            child: const Text(
              '💕',
              style: TextStyle(fontSize: 24),
            ))),
    Row(mainAxisAlignment: MainAxisAlignment.center, children: [
      const Text('🐰'),
      //⬇️ distance 사용 부분
      GetBuilder<DistanceController>(
          builder: (_) => AnimatedSize(
              duration: const Duration(milliseconds: 500),
              child: SizedBox(width: controller.distance))),
      const Text('🥕')
    ]),
  ],
),

변수가 사용되는 위젯을 GetBuilder로 감싸고, controller를 전달해서 변수를 꺼내쓰는 방식! builder에서 전달한 controller(여기선 '_')를 사용해도 되고, 앞에서 put 혹은 find로 선언한 controller를 써도 똑같이 작동을 하는데... 성능상 무슨 차이가 있는진 잘 모르겠움ㅎㅎ;;(무책임)

AnimatiedOpacity와 AnimatedSize는.. 부드러운 효과를 주려고 사용한 것!


🎩 Controller에서 정의한 메서드 호출하기

Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    ElevatedButton(
        onPressed: () {
          controller.getClose();
        },
        child: const Text('주기')),
    const SizedBox(width: 20),
    ElevatedButton(
        onPressed: () {
          controller.getFar();
        },
        child: const Text('뺏기')),
  ],
)

controller에서 그대로 메서드를 꺼내 쓰면 된당. 쏘 이지~~~




🪄 Reactive 방식

이번엔 반응형 상태 관리!

먼저 첫번째 방식과의 차이점이 뭐냐! 하면

  • state가 업데이트되어도 해당 값이 실제로 바뀌지 않으면 렌더링을 하지 않는다 (불필요한 렌더링 제거)
  • state값의 변화에 따라 ever, once, debounce, intervalWorkers를 등록해서 사용할 수 있다

그럼 사용법을 알아보자~~~


🎩 Controller 부분

제일 명확하게 다른 부분!

먼저 GetxController를 상속받지 않아도 되고, 상태값이 바뀔 때 update()도 호출하지 않는다.

대신 타입 선언 시 그냥 타입이 아니라 Rx를 붙인 타입(RxInt, RxString...)으로 선언하고, 초깃값 뒤에 .obs를 붙여주어야 한다. 또 선언 후 해당 값을 이용할 때는 변수명.value의 형태로 접근해야 한다.

class ReactiveDistanceController {
  RxDouble _distance = 100.0.obs;
  RxDouble get distance => _distance;

  RxBool _heart = false.obs;
  RxBool get heart => _heart;

  void getClose() {
    if (_distance.value == 0) {
      _heart.value = true;
      Future.delayed(const Duration(milliseconds: 500), () {
        _heart.value = false;
      });
      return;
    }
    _distance.value -= 20;
  }

  void getFar() {
    _distance.value += 20;
  }
}

🎩 Controller 선언하기

Get.put(ReactiveDistanceController());

final controller = Get.find<ReactiveDistanceController>();

Simple 방식과 똑 같 음!


🎩 Controller에서 정의한 변수 이용하기

Stack(
  alignment: Alignment.topCenter,
  children: <Widget>[
    Obx(() =>
      AnimatedOpacity(
          duration: const Duration(milliseconds: 500),
          opacity: controller.heart.value ? 1 : 0,
          child: const Text('💕', style: TextStyle(fontSize: 24),))),
    Row(mainAxisAlignment: MainAxisAlignment.center, children: [
      const Text('🐰'),
      Obx(() => AnimatedSize(
          duration: const Duration(milliseconds: 500),
          child: SizedBox(
              width: controller
                  .distance
                  .value))),
      const Text('🥕')
    ]),
  ],
),

GetBuilder 대신 Obx로 위젯을 감싸서 사용하면 되고, 이때 자식 함수는 따로 컨트롤러를 전달하지 않는다. GetBuilder와 거의 똑같이 쓸 수 있는 GetX 방식도 있움!

그리구 앞서 언급했듯 Rx변수 뒤에 .value를 붙여서 값을 사용해야 한다.


🎩 Controller에서 정의한 메서드 호출하기

Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    ElevatedButton(
        onPressed: () {
          controller.getClose();
        },
        child: const Text('주기')),
    const SizedBox(width: 20),
    ElevatedButton(
        onPressed: () {
          controller.getFar();
        },
        child: const Text('뺏기')),
  ],
)

이것도 첫 번째 방식과 차이가 없다>.<



결론: simple 방식은 사용 메모리가 적고, reactive 방식은 state값 변화에 따라 부가적인 기능을 제공한다. 프로젝트 규모나 상황에 따라 적절한 방식으로 쓰면 될 듯~~~!

Reference

[ Flutter / 플러터 ] GetX 주요 기능 3가지 중 그 두번째 상태관리 #3
[Flutter] GetX 정리 - Terry’s Dev-Diary

profile
DevelOpErUN 성장일기🌈

0개의 댓글