30일차에서는 29일차에 이어 GetX를 학습했다. GetX에서 데이터를 Observerble 하게 만들고, 이를 관찰하여 즉시 화면에 적용하도록 하는 방법을 학습했다.
학습한 내용
- GetX - Rx, Obx
- GetX - init, ever,once
GetX에는 Observerble(관측 가능한) 데이터 타입이 존재하는데 이를 Rx(Reactive)라고 한다.
이러한 변수를 선언하는 방식에는 세 가지 방법이 있다.
int num = 1; //관측 불가능한 int형 데이터
//관측 가능한 데이터
var num1 = 1.obs;
var num2 = RxInt(1);
RxInt num3 = 1.obs;
기존의 데이터 타입은 아래와 같은 데이터 타입으로 관측 가능한 데이터 타입을 선언할 수 있다.
기존 데이터 타입 | 관측 가능한 데이터 타입 |
---|---|
int | RxInt |
String | RxString |
List | RxList |
Map | RxMap |
Bool | RxBool |
커스텀 클래스의 경우
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); //함수 형태로 변경할 값을 전달하는 방식
GetX에서는 GetxController의 값을 가져와 가공하는 세 가지 방법이 있다. 크게 단순 상태관리와 반응형 상태관리로 나눌 수 있다.
단순 상태관리 : GetBuilder
반응형 상태관리: getX, Obx
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}',
),
),
이러한 GetBuilder
는 StatefulWidget
을 대신할 수 있다. 빠르고 메모리 비용이 저렴하지만, reactive 하지 않다는 특징이 있다.
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
도 역시 반응형 상태관리 방법이다. 데이터의 변경을 스스로 관찰하여 화면에 적용한다.
컨트롤러는 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에는 reactive 방식(반응형 상태관리)에서 사용할 수 있는 Workers가 있다. 이는 Rx 변수들의 변화를 감지하고 상황에 맞는 대응을 할 수 있다. Workers는 아래와 같이 4가지가 있다.
Workers
- once: 값이 처음 변경되었을 때만 호출
- ever: 값이 변경될 때마다 호출
- debounce: 값이 변경되다가 마지막 변경 후, 지정한 시간 동안 변경이 없을 때 호출
- interver: 값이 변경되고 있는 동안 지정한 시간마다 호출
사용 방식은 네 가지 Workers 모두 동일하며 debounce
와 interver
은 time
을 설정해야 한다. 방식은 아래와 같다.
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;
});
}
}
- 비트코인 앱 만들기
GetX를 사용하여 setState
없이 화면에 바로 적용되는 비트코인 앱을 만들고자 한다.
- 메인페이지에서 코인이 1초마다 +1되도록 한다.
- 비트코인은 FontAwesome의 아이콘을 사용한다.
Icon( FontAwesomeIcons.bitcoin, size: 96.0, color: Colors.yellow.shade700, ),
- CoinController를 만들고, GetxController를 extends한다.
- 코인은 int형의 데이터를 가지며, 관측 가능한 형태의 데이터타입을 사용한다.
- 이 때, Timer를 사용할 수 있도록 한다.
- 코인이 10의 배수가 될 때마다, 코인 10n개를 달성했다는 문구를 스낵바로 출력한다.(GetX의 워커중 상황에 맞는 올바른 워커를 사용할 것)
- 상점으로 이동하기 버튼을 누르면 상점 페이지로 이동한다.
- 상점 페이지에서도 보유한 코인을 볼 수 있다.
- 코인 리셋을 누르면 보유한 코인이 다시 0개로 바뀐다.
dependencies:
cupertino_icons: ^1.0.2
flutter:
sdk: flutter
font_awesome_flutter: ^10.4.0
get: ^4.6.5
pubspec.yaml
에 필요한 패키지를 설치했다.
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
를 호출한다.
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개 돌파', '축하합니다!');
}
});
}
}
CoinController
는 GetxController
를 상속받고, 관찰 가능한 RxInt
타입의 coin
을 멤버 변수로 가진다.
초기화를 하는 onInit()
에서는 Timer
를 사용해 1초마다 coin
의 값이 1씩 증가하도록 설정했고, ever
워커를 사용해 값이 변경될 때마다 실행될 코드를 작성했다. 해당 워커에서는 coin
의 값이 10의 배수가 될 때마다 스낵바를 출력한다.
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
로 페이지를 이동했다.
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으로 초기화했다.
오늘은 과제가 생각보다는 쉬웠다. 금방 끝내기도 했고, GetX가 확실히 쉬운것 같다. 예전에 Provider 썼을 때는 되게 어려웠던 것 같은데...ㅎㅎ 강의에서 테디님이 GetX에 너무 의존하면 안 된다고 하신 이유를 알 것 같다.ㅋㅋㅋㅋ 과제가 좀 빨리 끝나서 오늘은 학습한 내용들을 자세히 정리했다.