이 글은 플러터에서 사용 가능한 프레임워크 중 하나인 GetX에 대한 글입니다. 공식 github repository의 README.md에서 얻은 정보를 재구성한 내용임을 알려드립니다.
이 패키지의 목표는 당신에게 최소의 종속성(pubspec의 dependencies)으로 라우트/상태/종속성 관리를 위한 완전한 솔루션을 제공하는 것입니다.
이러한 철학을 바탕으로 GetX에서는 3가지 Manager를 제공합니다. 바로 상태 관리자, 종속성 (혹은 의존성) 관리자, 그리고 라우트 관리자입니다. 3가지 관리자 모두에 해당되는 GetX의 가장 큰 특징은 바로, '간결한 코드'입니다. 이번 글에서는 상태 관리자에 대해 다룹니다.
GetX 상태 관리자는 2가지가 있습니다.
먼저 simple manager부터 알아봅시다.
지금껏 플러터에서 흔히 사용된 Provider-Consumer 패턴과 아주 유사합니다.
getBuilder
를 사용합니다.// 컨트롤러 파일
class BuilderController extends GetxController {
var count = 0;
increment() {
count++;
update(); // => 기존 Provider 패턴에서 notify 역할을 합니다.
}
}
// 뷰 파일
GetBuilder<BuilderController> ( //타입 명시 안 하면 버그
init: BuilderController(),
builder: (_) {
return Text("count: ${_.count}");
},
),
// GetBuilder 밖에서 컨트롤러 찾기
Get.find<BuilderController>().increment();
controller.increment();
어디서 많이 본 것 같은 패턴이지 않나요? 기존 방식처럼 update()
라는 코드를 직접 적어줘야 상태가 바뀐 것을 알릴 수 있기 때문에 '수동적인 상태 관리자'라고도 부릅니다.
아래 코드는 기존 뷰에서 initState()를 해주던 방식과 유사합니다.
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
)
하지만 문서에서는 빌더가 아닌 컨트롤러 코드를 적는 것을 권장하고 있습니다.
void onInit() { // initState()
fetchApi();
super.onInit();
}
void onClose() { // stream 닫기
// ...
super.onClose();
}
위젯에 아이디를 지정해서, 해당 위젯에서만 재 랜더링이 일어나게 할 수 있습니다. 그러니까 똑같은 컨트롤러 A로부터 상태를 받고 있는 위젯 X, Y가 있다면 X, Y에 각각 ID를 부여해서 원하는 위젯 한 쪽에만 다시 랜더링이 일어나게 할 수 있습니다.
또한 업데이트 함수에 추가적인 parameter를 넘겨서, 원하는 조건이 만족될 때만 랜더링이 일어나도록 할 수 있습니다. 이 때 랜더링만 다시 되지 않는 것이고 값은 업데이트됩니다.
아래 예제에서는 counter
변수 값이 5 이하일 때, wanna update
이라는 아이디를 갖고 있는 첫 번째 위젯만 재 랜더링이 일어납니다.
// 컨트롤러 파일
class BuilderController extends GetxController {
final counter = 0.obs;
increment() {
counter.value++;
update(["wanna update"], counter <= 5);
}
}
// 뷰 파일
GetBuilder<BuilderController>(
id: "wanna update",
init: controller,
builder: (_) => Text(
'${Get.find<BuilderController>().counter.value}',
style: TextStyle(fontSize: 30),
),
),
GetBuilder<BuilderController>(
id: "don't wanna update",
builder: (_) => Text(
'${Get.find<BuilderController>().counter.value}',
style: TextStyle(fontSize: 30),
),
),
GetX 고유의 상태 관리자라고 할 수 있습니다.
Obx
를 사용합니다.// 컨트롤러 파일
class Controller extends GetxController {
final count = 0.obs;
increment() => count.value++;
}
// 뷰 파일
final Controller controller = Get.put(Controller());
// ...
Obx(() => Text("count: ${controller.count.value}"));
= Obx
함수를 사용해서 변화를 감지할 수 있는 변수입니다.
update()
함수가 필요없다는 의미!모든 변수는 observable 변수로 만들 수 있습니다.
다트가 null safety를 지향하기(로 했기) 때문에, 선언과 동시에 초기화 해 주는 것을 권장하고 있습니다.
Rx{type}
사용: 초기값 설정이 필수는 아니지만 권장됨. final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
Rx<type>
사용 final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
.obs
사용 final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
공식 문서의 예제에서는 3번 방식을 가장 많이 사용하고 있습니다.
.value
primitive type (int
, String
등)값에 접근할 때 사용하고, 클래스 인스턴스에 접근할 때에도 필요합니다. 하지만 리스트 접근 시에는 없어도 됩니다.
final title = "this is a title".obs;
Text(controller.title.value);
final user = User().obs;
Text("Name ${user.value.name}: Age: ${user.value.age}")
final list = [1, 2, 3, 4, 5].obs;
ListView.builder (
itemCount: controller.list.length // lists don't need it
);
reactive manager 방식을 쓸 때에만 사용 가능한 콜백 함수입니다. 총 4가지가 있습니다. (1, 2번은 대상의 타입만 다르고 하는 일은 같습니다.)
ever
: 변수가 업데이트 될 때마다 실행everAll
: ever 와 같은 역할. 대신 리스트를 다룰 때 사용once
: 변수의 값이 첫번째로 바뀌었을 때만 실행debounce
: 변수의 값이 마지막으로 바뀐 지 특정 시간이 지난 후에 실행interval
: 변수의 값이 바뀌는동안 특정 시간마다 실행공식문서 피셜, 쓰면 좋을 것 같은 상황은
debounce
: 검색 API입니다. 사용자가 '고구마'를 검색하고자 할 때 'ㄱ', 'ㅗ', 'ㄱ', ... 이런식으로 한 글자씩 입력 할 때마다 검색 request를 보내면 매우 비효율적입니다. debounce를 사용하면 사용자가 빠르게 '고ㄱ' 까지 입력하다가 잠깐 쉬는 타이밍, 이 타이밍을 감지해서 그 때에만 한번 실행됩니다. 사용자가 다시 입력을 시작해서 '고구마'까지 완성시키고 동작을 멈추면 그 때 또 한번 실행됩니다. 따라서 '실시간 검색이 되는 것처럼 보이는' 효과를 줄 수 있죠.
init
등 좀 더 세분화된 커스터마이징이 필요할 때"개별" 위젯이 많은 경우에는 GetX의 성능이 뛰어나고, 상태 변화가 여러 번 일어나는 경우에는 GetBuilder의 성능이 뛰어납니다.
GetX에서는 컨트리뷰터를 환영합니다!🎉
저도 독스를 읽으면서 오타 및 자연스럽지 않은 문장이 있어 풀리퀘를 넣었는데, 주인장이 꽤 빠르게 반응을 해줬습니다. 2번째 풀리퀘는 넣자마자 거의 1분도 되지 않아 머지 되어서 놀랬던 기억이 있네요. 아무튼 한국어로 번역된 README.md도 있으니 가서 이상한 부분이 있으면 고쳐줍시다. 😇
작고 귀여운 내 컨트리뷰트 내역 ^,^
아무 생각 없이 번역 시작했다가 생각보다 너무 길어서 지쳤었는데, 그래서 그런지 번역을 매끄럽게 못하고 오타도 있었나 보네요.