[Flutter] 스나이퍼팩토리 30일차

KWANWOO·2023년 3월 7일
1
post-thumbnail

스나이퍼팩토리 플러터 30일차

30일차에서는 29일차에 이어 GetX를 학습했다. GetX에서 데이터를 Observerble 하게 만들고, 이를 관찰하여 즉시 화면에 적용하도록 하는 방법을 학습했다.

학습한 내용

  • GetX - Rx, Obx
  • GetX - init, ever,once

추가 내용 정리

Rx (Reactive) 타입

GetX에는 Observerble(관측 가능한) 데이터 타입이 존재하는데 이를 Rx(Reactive)라고 한다.

이러한 변수를 선언하는 방식에는 세 가지 방법이 있다.

int num = 1; //관측 불가능한 int형 데이터

//관측 가능한 데이터
var num1 = 1.obs;
var num2 = RxInt(1);
RxInt num3 = 1.obs;

기존의 데이터 타입은 아래와 같은 데이터 타입으로 관측 가능한 데이터 타입을 선언할 수 있다.

기존 데이터 타입관측 가능한 데이터 타입
intRxInt
StringRxString
ListRxList
MapRxMap
BoolRxBool

커스텀 클래스의 경우 Rx<User>, RxList<User>와 같은 방식으로 사용하고 만약 nullable 한 경우 Rxn<User>, RxnList<User>와 같은 형태로 사용할 수 있다.

이렇게 선언한 데이터는 Rx 타입으로 옵저버 형태를 가지기 때문에 데이터에 접근하기 위해서는 아래와 같이 .value를 사용해야 한다

RxString name = 'Kim'.obs;
print(name.value) //Kim 출력

단, 리스트에서는 위와 같이 .value를 사용할 필요가 없다. 예를 들어 리스트의 길이를 가져오려면 list.value.length가 아닌 원래 방식대로 list.length를 사용하면 된다.

Rx 타입에 새로운 값을 넣얼 때는 아래와 같이 두 가지 방식을 사용할 수 있는데 함수 형태로 값을 넣어주는 방식이 더 좋다.

RxInt num = 0.obs;

num = 1.obs; //RxInt 전체를 변경하는 방식
num(2); //함수 형태로 변경할 값을 전달하는 방식

GetxController의 값을 가져와 가공하는 세가지 방법

GetX에서는 GetxController의 값을 가져와 가공하는 세 가지 방법이 있다. 크게 단순 상태관리와 반응형 상태관리로 나눌 수 있다.

단순 상태관리 : GetBuilder
반응형 상태관리: getX, Obx

GetBuilder

GetBuilder는 단순 상태관리로 상태 값이 변경된 뒤에 update() 함수를 사용하여 상태가 변경되었음을 아려주어야 한다. update()로 상태 변경을 알리지 않으면 값은 변경되지만 화면에 적용이 되지 않는다.

update()함수는 컨트롤러의 값이 변경되었음을 알리는 역할을 한다.

예를 들어 아래와 같은 GetxController를 작성했다고 하자.

class Controller extends GetxController {
  int counter = 0;

  void increment() {
    counter++;
    update(); // 변화를 notifty 한다.
  }
}

GetBuilder를 사용하면 이 컨트롤러의 값을 가져다 사용할 수 있다. 여기서 init속성은 Get.put의 역할을 한다.

GetBuilder<Controller>(
  init: Controller(), //app의 어디서든 최초 1회만 호출 하면 된다.
  builder: (value) => Text(
    '${value.counter}',
  ),
),

이러한 GetBuilderStatefulWidget을 대신할 수 있다. 빠르고 메모리 비용이 저렴하지만, reactive 하지 않다는 특징이 있다.

getX

getX는 반응형 상태관리 방법 중 하나로 stream을 사용한다. 또한 Rx 타입의 데이터를 사용해야 한다.

이 방식은 반응형 상태관리로 아래 컨트롤러처럼 GetBuilder와 달리 변경되었을 때 update()를 실행할 필요가 없다.

class Controller extends GetXController {
  var count = 0.obs;  // RxInt 타입

  void iuncrement() => counter.value++;
}

해당 컨트롤러의 값을 아래 예시 코드처럼 불러와 사용할 수 있다. 역시 init 속성은 초기 한 번만 호출하면 되며, Get.put과 같이 컨트롤러를 생성하는 역할을 한다.

GetX<Controller>(
  init: Controller(),
  builder: (val) => Text(
    '${val.counter.value}',
  ),
),

getX 방식은 value가 실제로 바뀌었을 때만 화면을 다시 그리며, 컨트롤러를 인스턴스화 하지 않는다.

Obx

Obx도 역시 반응형 상태관리 방법이다. 데이터의 변경을 스스로 관찰하여 화면에 적용한다.

컨트롤러는 Get.put을 사용해 생성하고, Get.find를 사용해 값을 불러와 사용할 수 있다.

예시로 먼저 아래와 같이 User 클래스와 GetxController를 작성했다.

class User {
  String? name;
  User({this.name});
}


class Controller extends GetxController{
  var user = user{name: "cat"}.obs;   // 사용자 정의 User 클래스
  
  void changeName() => user.value.name = "Garg";  // value로 접근
}

원하는 페이지에서 아래와 같이 Get.put을 사용해 컨트롤러를 등록할 수 있다.

class FirstPage extends StatelessWidget {

  // contriller binding 필요
  Controller controller = Get.put(Controller());    

}

초기 한 번만 컨트롤러를 등록하면 다른 페이지에서 Get.find로 컨트롤러를 가져올 수 있다.

class PageSeven extends StatelessWidget{

  // 다른 widget에서 controller에 그대로 access 가능
  Controller controller = Get.find<Controller>(); 
  
}

위와 같이 컨트롤러를 가져와 Obx로 데이터를 관찰하고 출력할 수 있다.

Obx(() => Text( //user 변화를 관찰 및 적용
  '${controller.user.value.name}'
 ),
),

Obx는 문법이 간단하여 사용하기가 쉽고, 하나의 위젯에서 여러 개의 컨트롤러를 사용할 수 있다는 장점을 가지고 있다.

GetX Workers

GetX에는 reactive 방식(반응형 상태관리)에서 사용할 수 있는 Workers가 있다. 이는 Rx 변수들의 변화를 감지하고 상황에 맞는 대응을 할 수 있다. Workers는 아래와 같이 4가지가 있다.

Workers

  • once: 값이 처음 변경되었을 때만 호출
  • ever: 값이 변경될 때마다 호출
  • debounce: 값이 변경되다가 마지막 변경 후, 지정한 시간 동안 변경이 없을 때 호출
  • interver: 값이 변경되고 있는 동안 지정한 시간마다 호출

사용 방식은 네 가지 Workers 모두 동일하며 debounceintervertime을 설정해야 한다. 방식은 아래와 같다.

workers(관찰할 변수, (변경된 값) {
		//실행할 코드
	},
    time: Duration(시간 설정), //debounce와 interver에 설정
);

Workers는 보통 컨트롤러에서 GetxController가 생성되고 실행되는 onInit()에 작성한다. 아래는 예시 코드이다.

void onInit() {
  super.onInit();

  once(count2, (_) {
    print('$_이 처음으로 변경되었습니다.');
  });
  
  ever(count2, (_) {
    print('$_이 변경되었습니다.');
  });
  
  debounce(
    count2,
    (_) {
      print('$_가 마지막으로 변경된 이후, 1초간 변경이 없습니다.');
    },
    time: Duration(seconds: 1),
  );
  
  interval(
    count2,
    (_) {
      print('$_가 변경되는 중입니다.(1초마다 호출)');
    },
    time: Duration(seconds: 1),
  );
}

실제로는 앱에서 유저가 로그인하여 데이터가 변경되면 페이지를 이동 시킬 때 아래 코드처럼 사용 가능하다.

class Controller extends GetxController {
  Rxn<User> userInfo = Rxn<User>();

  void onInit() {
  	super.onInit();

    ever(userInfo, (newData) {
        if(newData != null) {
            Get.to(() => MainPage());
            return;
        }
        Get.to(() => LoginPage());
        return;
    });
  }
}

30일차 과제

  1. 비트코인 앱 만들기

1. 비트코인 앱 만들기

GetX를 사용하여 setState없이 화면에 바로 적용되는 비트코인 앱을 만들고자 한다.

요구사항

  • 메인페이지에서 코인이 1초마다 +1되도록 한다.
  • 비트코인은 FontAwesome의 아이콘을 사용한다.
Icon(
    FontAwesomeIcons.bitcoin,
    size: 96.0,
    color: Colors.yellow.shade700,
),
  • CoinController를 만들고, GetxController를 extends한다.
    • 코인은 int형의 데이터를 가지며, 관측 가능한 형태의 데이터타입을 사용한다.
    • 이 때, Timer를 사용할 수 있도록 한다.
    • 코인이 10의 배수가 될 때마다, 코인 10n개를 달성했다는 문구를 스낵바로 출력한다.(GetX의 워커중 상황에 맞는 올바른 워커를 사용할 것)
  • 상점으로 이동하기 버튼을 누르면 상점 페이지로 이동한다.
    • 상점 페이지에서도 보유한 코인을 볼 수 있다.
    • 코인 리셋을 누르면 보유한 코인이 다시 0개로 바뀐다.

결과물 예시

코드 작성

  • pubspec.yaml
dependencies:
  cupertino_icons: ^1.0.2
  flutter:
    sdk: flutter
  font_awesome_flutter: ^10.4.0
  get: ^4.6.5

pubspec.yaml에 필요한 패키지를 설치했다.

  • lib/main.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:new_app/controller/coin_controller.dart';
import 'package:new_app/page/main_page.dart';

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

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

  
  Widget build(BuildContext context) {
    Get.put(CoinController(coin: 0.obs)); //전역 컨트롤러 생성
    return const GetMaterialApp(
      home: MainPage(), //MainPage 호출
    );
  }
}

main.dart에서는 전역 컨트롤러를 생성하고 MainPage를 호출한다.

  • lib/controller/coin_controller.dart
import 'dart:async';

import 'package:get/get.dart';

class CoinController extends GetxController {
  RxInt coin; //보유 코인 수

  CoinController({
    required this.coin,
  });

  
  void onInit() {
    super.onInit();
    //1초마다 보유 코인 수 1 증가
    Timer.periodic(const Duration(seconds: 1), (timer) {
      coin(coin.value + 1);
    });

    //coin의 값이 변경될 때마다 실행
    //코인의 개수가 10의 배수가 될 때마다 스낵바 출력
    ever(coin, (callback) {
      if (callback != 0 && callback % 10 == 0) {
        Get.snackbar('코인 $callback개 돌파', '축하합니다!');
      }
    });
  }
}

CoinControllerGetxController를 상속받고, 관찰 가능한 RxInt 타입의 coin을 멤버 변수로 가진다.

초기화를 하는 onInit()에서는 Timer를 사용해 1초마다 coin의 값이 1씩 증가하도록 설정했고, ever 워커를 사용해 값이 변경될 때마다 실행될 코드를 작성했다. 해당 워커에서는 coin의 값이 10의 배수가 될 때마다 스낵바를 출력한다.

  • lib/page/main_page.dart
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:new_app/controller/coin_controller.dart';
import 'package:new_app/page/store_page.dart';

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

  
  Widget build(BuildContext context) {
    var controller = Get.find<CoinController>(); //컨트롤러 가져오기
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            //코인의 수를 관찰하여 출력
            Obx(() => Text(
                  style: const TextStyle(
                    fontSize: 24,
                  ),
                  'Current coin : ${controller.coin.value}',
                )),
            const SizedBox(height: 16),
            //비트코인 아이콘
            Icon(
              FontAwesomeIcons.bitcoin,
              size: 96.0,
              color: Colors.yellow.shade700,
            ),
            //상점으로 이동 버튼
            TextButton(
              onPressed: () => Get.to(() => const StorePage()),
              child: const Text('상점으로 이동하기'),
            ),
          ],
        ),
      ),
    );
  }
}

MainPage에서는 우선 전역 컨트롤러를 가져온다. 이를 사용해 코인의 개수를 출력하는데 값이 변경되는 것을 바로 화면에 적용할 수 있도록 Obx를 사용했다.

비트코인 아이콘은 FontAwesomeIcons를 사용했고, 텍스트 버튼을 넣어 눌렀을 때 Get.to로 페이지를 이동했다.

  • lib/page/store_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:new_app/controller/coin_controller.dart';

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

  
  Widget build(BuildContext context) {
    var controller = Get.find<CoinController>(); //컨트롤러 가져오기
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              style: TextStyle(
                fontSize: 24,
              ),
              '상점',
            ),
            const SizedBox(height: 16),
            //코인의 수를 관찰하여 출력
            Obx(() => Text('현재 보유한 코인 : ${controller.coin.value}')),
            //보유 코인의 수를 0으로 초기화
            TextButton(
              onPressed: () => controller.coin(0),
              child: const Text('코인리셋'),
            ),
          ],
        ),
      ),
    );
  }
}

StorePage는 상점 페이지로 먼저 Get.find로 컨트롤러를 가져온다.

상점 페이지도 역시 Obx를 사용해 코인의 수를 출력했다.

코인리셋 버튼을 클릭하면 컨트롤러의 coin을 0으로 초기화했다.

결과


벌써 30일차...

오늘은 과제가 생각보다는 쉬웠다. 금방 끝내기도 했고, GetX가 확실히 쉬운것 같다. 예전에 Provider 썼을 때는 되게 어려웠던 것 같은데...ㅎㅎ 강의에서 테디님이 GetX에 너무 의존하면 안 된다고 하신 이유를 알 것 같다.ㅋㅋㅋㅋ 과제가 좀 빨리 끝나서 오늘은 학습한 내용들을 자세히 정리했다.

📄Reference

profile
관우로그

0개의 댓글

관련 채용 정보