Flutter를 위한 매우 가볍고 강력한 라이브러리이며 강력함을 뒷받침 하고 있는 3가지 기본 원칙이 있으며 다음과 같다.
1.생산성
같은 기능도 더욱더 편하고 간결하게 표현이 가능하다.
컨트롤러들을 사용하고 반환시켜주는 처리를 신경 쓰지 않아도 알아서 GetX에서 사용되지 않을 때 제거해주기 때문에 개발자들은 더욱더 개발에만 신경 쓸 수 있다.
2.성능
GetX는 성능과 최소한의 리소스 소비에 중점을 둔다.
GetX는 Streams나 ChangeNotifier를 사용하지 않는다.
최소의 재 빌드를 위해 똑똑한 알고리즘을 적용하기 위해, GetX는 상태가 변했는지 확인하는 comparator를 사용한다.
3.조직화
GetX는 화면, 비즈니스 로직, 종속성 주입 및 내비게이션을 완전히 분리하여 관리할 수 있다.
GetX는 자체 종속성 주입 기능을 사용하여 DI를 뷰에서 완전히 분리하기 때문에 다중 Provider를 통해 위젯 트리에서 컨트롤러/모델/블록으로 주입할 필요가 없다.
stateless만 사용하는 이유
보통 Flutter에서 앱을 만들때 변수를 변경하거나 관리하기 위해 StatefulWidget을 사용한다.
하지만 GetX라이브러리는 StatefulWidget의 사용 대신 .obs 관찰 연산자를 통해 변수를 Observable로 바꾸는데 사용한다.
이로 인해 GetX라이브러리 내에서는StatefulWidget은 사용하지 않고 StatelessWidget을 사용하게 되는것이다.
GetX에는 크게 단순형(simple)과 반응형(Reactive) 이렇게 두가지 상태 관리법이 존재한다.
1.Simple방식
첫번째 방식은 비교적 간단한 방식이다. 이 방식은 이후에 설명할 reactive 방식보다 메모리를 적게 사용한다는 장점이 있다.
GetxController를 extend하는 Controller 클래스를 선언하고, 초기값을 0으로 설정한 count1 변수를 선언한다.
class Controller extends GetxController {
var count1 = 0;
}
GetBuilder을 통해 화면에 count1 변수를 보여준다. 이때 init을 설정하지 않으면 에러가 발생하는 것을 유의하자.
GetBuilder<Controller>(
init: Controller(),
builder: (_) => Text(
'clicks: ${_.count1}',
),
)
count1의 초기값인 0이 보이는 것을 확인할 수 있다.
이제 count1 변수를 증가시켜야 한다. 또한 증가할 때 이를 화면에 알려줘야한다. 이를 위해 update() 함수를 사용한다.
class Controller extends GetxController {
var count1 = 0;
void increment1() {
count1++;
update();
}
}
update()는 ChangeNotifier의 notifyListeners()와 동일하게 생각하면 된다. count1++를 통해 count1을 증가시킨 후 update()를 호출하는 increment1()을 정의한다.
Get.find(), Get.put()는 순서상 여기에 들어가는 것이 매끄러워서 넣은 것이지, simple 방식에 해당하는 것은 아니다. 두 방식(simple, reactive) 모두에서 사용된다.
Get.find()을 사용하여 increment1()을 호출하는 버튼을 만들어 텍스트 아래에 배치한다.
TextButton(onPressed: Get.find().increment1, child: Text('increment1'))
하지만 리빌드해보면 Get.find()에서 에러가 발생할 것이다. 이는 Get.find()가 Controller를 찾는 시점이 GetBuilder()의 init에서 Controller를 등록하기 이전이라서 그렇다.
Get.find(), Get.put()는 순서상 여기에 들어가는 것이 매끄러워서 넣은 것이지, simple 방식에 해당하는 것은 아니다. 두 방식(simple, reactive) 모두에서 사용된다.
이 문제를 해결하기 위해서 Get.put()을 사용한다.
build() 메소드 내부에서 Get.put()를 통해 Controller를 등록하여 이를 controller 변수에 할당한다.
build(BuildContext context) {
final controller = Get.put(Controller());
// ...
}
Widget
위의 과정에서 Controller를 등록한 것이기 때문에 GetBuilder에서 또 등록할 필요가 없다. 따라서 init 부분을 지운다.
GetBuilder<Controller>(
// init 부분 삭제.
builder: (_) => Text(
'clicks: ${_.count1}',
),
)
버튼에서 increment1()를 호출할 때, Get.find() 대신 controller 변수를 사용한다.
TextButton(onPressed: controller.increment1, child: Text('increment1')),
리빌드 해보면 에러가 발생하지 않을 것이다. 또한 버튼을 클릭해보면 화면의 숫자가 증가하는 것을 확인할 수 있다.
2.Reactive방식
simple 방식은 메모리를 적게 사용한다는 장점이 있었다. 그렇다면 reactive 방식은 무슨 장점이 있고 어느 경우에 사용할까?
reactive 방식에는 simple 방식에는 없는 특별한 기능이 있다. 이에 대해선 reactive 방식에 대한 기본 설명 이후에 정리해보도록 하겠다.
reactive 방식에서는 observable 변수라는 특별한 변수를 사용한다. observable 변수를 Rx라고도 부른다. 이런 Rx를 선언하는 방법에는 아래와 같이 3가지가 있다.
Value.obs
Rx(Value)
RxType(Value)
이 중 가장 간단한 1번 방법을 주로 사용한다. 우리도 1번 방법을 사용하여 count2 변수를 정의해보자.
var count2 = 0.obs; // 1번
var count2 = Rx<int>(0); // 2번
var count2 = RxInt(0); // 3번
Rx의 값을 접근할 때는 일반적인 변수의 값의 경우와 다르게 .value를 통해 접근할 수 있다. 여기서 주의해야할 점이 있다. String과 int 같은 primitive type에는 .value를 사용해야하지만, List에서는 .value가 필요없다. dart api가 리스트에서만 .value 없이도 값에 접근할 수 있게 해주기 때문이다.
void increment2() => count2.value++;
.value를 사용해서 count2의 값을 1 증가시키는 increment2() 함수를 정의했다. reactive 방식에선 update() 함수가 필요하지 않다.
simple 방식의 GetBuilder과 같은 역할을 하는 것이 GetX이다. 그럼 GetX를 사용해서 count2의 값을 보여주는 텍스트를 만들어보자.
GetX<Controller>(
builder: (_) => Text(
'clicks: ${_.count2.value}',
),
),
이전 simple 방식 예시(count1) 예시에 count2 코드를 추가하고 있기 때문에 이미 Get.put()을 통해 Controller를 등록한 상태이다. 따라서 GetX에 init 부분을 넣지 않았다. 필요한 경우에는 GetBuilder에서처럼 init을 통해 Controller를 등록할 수 있다.
count2의 초기값인 0이 보이는 것을 확인할 수 있다.
GetX보다 더 간단한 방법이 있다. 바로 Obx()를 사용하는 것이다. Obx()의 경우 사용할 컨트롤러의 종류를 따로 명시할 필요가 없고, 보여줄 위젯만 리턴하면 된다. 하지만 이 방법은 무조건 Get.put()을 필요로 한다.
Obx를 사용해서 count2의 값을 보여주는 텍스트를 만들어서 GetX를 통해 만든 텍스트 하단에 배치하자.
Obx(() {
return Text(
'clicks: ${controller.count2.value}',
);
}),
이미 앞선 예시에서 Get.put()을 통해 선언한 controller가 존재하기 때문에 이를 바로 사용했다.
count2의 초기값인 0이 보이는 것을 확인할 수 있다.
마지막으로 count1 예시에서처럼 count2를 증가시키는 버튼을 만든다.
TextButton(onPressed: controller.increment2, child: Text('increment2'))
버튼을 클릭하면 GetX()를 통해 만든 텍스트와 Obx()를 통해 만든 텍스트의 숫자가 모두 증가하는 것을 확인할 수 있다.
이전에 말했던 reactive 방식에서만 사용할 수 있는 특별한 기능들이 바로 workers이다. 이를 사용하면 Rx 변수들의 변화를 감지하고 다양한 상황 별로 적절한 대응을 할 수 있다.
workers에는 아래와 같이 총 4가지가 있다.
// count2가 처음으로 변경되었을 때만 호출된다.
once(count2, (_) {
print('$_이 처음으로 변경되었습니다.');
});
// count2가 변경될 때마다 호출된다.
ever(count2, (_) {
print('$_이 변경되었습니다.');
});
// count2가 변경되다가 마지막 변경 후, 1초간 변경이 없을 때 호출된다.
debounce(
count2,
(_) {
print('$_가 마지막으로 변경된 이후, 1초간 변경이 없습니다.');
},
time: Duration(seconds: 1),
);
// count2가 변경되고 있는 동안, 1초마다 호출된다.
interval(
count2,
(_) {
print('$_가 변경되는 중입니다.(1초마다 호출)');
},
time: Duration(seconds: 1),
);
이 4가지의 workers를 Controller에 적용해보자.
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),
);
}
Controller에 onInit()을 override한다. 그 다음 사용하고자 하는 worker를 등록해주면 된다. 이때 super.onInit() 호출을 잊지 말자.