UI에서 실시간으로 변하는 여러 데이터들의 상태를 효율적으로 관리하기 위한 개념이다.
예를 들어, 인스타그램과 같은 서비스에서 사용자가 댓글을 입력하고 업로드 하는 순간 댓글 목록에 새로운 댓글이 보여져야 한다. 화면 관점에서 새로운 데이터가 생기고 그에 따라 새로운 UI를 그려주어야 하기 때문에 플러터는 statefulWidget으로 해당 화면을 재렌더링 하게 된다. 그 외에도 댓글의 하트를 누를 때, 댓글을 삭제 또는 수정할 때도 화면의 일부를 변경하기 위해서 화면 전체를 재렌더링 하게 된다. 하지만 이는 매우 비효율적이다.
또한 해당 글에 하트를 눌러서 하트 UI가 빨간색 하트로 변경 되었을 때 다른 페이지에서도 이를 참고하고 있다면 해당 페이지에서도 하트가 빨간색으로 변경되어야 한다.
상태관리 기술을 사용하게 되면 위와 같이 실시간으로 변화하는 데이터에 대한 처리와 여러 컴포넌트에서 공통적으로 사용하는 데이터의 동기화를 아주 쉽고 효율적으로 해결할 수 있다.
GetX를 통한 상태관리 방식
1. 단순 상태 관리 : 기존 데이터와 변경되는 데이터가 같은지 확인하지 않음
2. 반응형 상태 관리 : 데이터에 변화가 있을 때만 재렌더링
import 'package:get/get.dart';
class SimpleController extends GetxController {
int counter = 0;
void increase() {
counter++;
update();
}
}
이 controller는 counter
라는 변수와 counter의 값을 1씩 증가해주는 increase()
함수를 가지고 있다. increase() 함수 안에 update()
는 이 controller를 바라보고 있는 모든 코드에 업데이트를 알리는 역할을 한다.
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
Get.put(SimpleController()); // controller 등록
return Scaffold(
appBar: AppBar(
title: const Text("단순 상태관리"),
),
body: Center(
child: GetBuilder<SimpleController>( // 실시간 렌더링
builder: (controller) {
return ElevatedButton(
child: Text(
'현재 숫자: ${controller.counter}',
),
onPressed: () {
controller.increase();
// Get.find<SimpleController>().increase();
},
);
},
),
),
);
}
}
위에서 만들어준 controller를 사용하는 화면 클래스이다. 먼저 controller를 사용하기 위해 Get.put()
으로 컨트롤러를 등록해준다. GetBuilder 아래의 모든 위젯은 controller에서 변경되는 데이터를 실시간으로 반영할 수 있는 상태가 된다. controller.counter
는 변수를 실시간으로 반영하게 되고 controller.increase()
는 controller의 counter 데이터를 실시간으로 증가시키게 된다. 만약 GetBuilder를 사용하지 않을 경우 Get.find<[Controller종류]>().[변수 혹은 함수]
로 컨트롤러의 데이터를 실시간 변경 혹은 반영할 수 있다.
import 'package:get/get.dart';
class ReactiveController extends GetxController {
RxInt counter = 0.obs;
void increase() {
counter++;
}
}
이 controller는 counter
라는 변수와 counter의 값을 1씩 증가해주는 increase()
함수를 가지고 있다. 단순 상태 관리와 비교하면 변수를 선언하는 방식과 업데이트 함수 부분이 다르다. 변수를 선언하는 방식은 변수의 타입을 RxInt, RxString 등 Rx{타입}
의 방식으로 선언하고 변수의 값은 .obs
를 붙이게 된다. 업데이트의 경우 update 함수를 부르지 않아도 된다.
Widget build(BuildContext context) {
Get.put(SimpleController()); // 단순 상태 관리 controller 등록
Get.put(ReactiveController()); // 반응형 상태 관리 controller 등록
return Scaffold(
appBar: AppBar(
...
GetX<ReactiveController>( // 반응형 상태관리 - 1
builder: (controller) {
return ElevatedButton(
child: Text('반응형 1 / 현재 숫자: ${controller.counter.value}'), // .value 로 접근
onPressed: () {
controller.increase();
// Get.find<ReactiveController>().increase();
},
);
},
),
Obx( // 반응형 상태관리 - 2
() {
return ElevatedButton(
child: Text('반응형 2 / 현재 숫자: ${Get.find<ReactiveController>().counter.value}'), // .value 로 접근
onPressed: () {
Get.find<ReactiveController>().increase();
},
);
},
),
단순 상태관리와 동일하게 controller를 사용하기 위해 Get.put()
으로 컨트롤러를 등록해준다. 반응형 상태관리에서 데이터를 실시간으로 반영하는 방식에는 두가지가 있다.
controller.counter.value
는 controller의 변수를 실시간으로 반영하게 되고 controller.increase()
는 controller의 counter 데이터를 실시간으로 증가시키게 된다. 만약 GetX를 사용하지 않을 경우 Get.find<[Controller 종류]>().[변수 혹은 함수]
로 컨트롤러의 데이터를 실시간 변경 혹은 반영할 수 있다.Get.find()
방식으로 접근해야 한다.반응형 상태관리에서는 worker라는 추가 기능이 있다. Worker는 controller 안에서 onInit() 함수를 override하고 그 안에 추가해서 사용하게 되는데 아래의 4가지 종류가 있다.
import 'package:get/get.dart';
class ReactiveController extends GetxController {
static ReactiveController get to => Get.find();
RxInt counter = 0.obs;
void onInit() {
once(counter, (_) {
print('once : $_이 처음으로 변경되었습니다.');
});
ever(counter, (_) {
print('ever : $_이 변경되었습니다.');
});
debounce(
counter,
(_) {
print('debounce : $_가 마지막으로 변경된 이후, 1초간 변경이 없습니다.');
},
time: Duration(seconds: 1),
);
interval(
counter,
(_) {
print('interval $_가 변경되는 중입니다.(1초마다 호출)');
},
time: Duration(seconds: 1),
);
super.onInit();
}
void increase() {
counter++;
}
}
Get.find<[Controller종류]>().[변수 혹은 함수]
를 보다 간단하게 사용하기 위해서는 아래와 같이 controller 내부에 getter를 생성해주면 된다.class SimpleController extends GetxController {
static SimpleController get to => Get.find();
...
}
// 전
Get.find<SimpleController>().increase();
// 후
SimpleController.to.increase();
// 전
class SimpleState extends StatelessWidget{}
// 후
class SimpleState extends GetView<SimpleController>{}
// 전
Get.find<SimpleController>().increase();
// 후
controller.increase();