본문 바로가기

일::개발

Flutter GetX: 하나의 Controller를 사용하는 페이지 간 이동할 때의 문제 (2)

이전 포스트(https://uaremine.tistory.com/35)에서 M -> A1 -> A2 이동 시에 발생하는 문제를 확인했다.

 

처음에 이 상황에서 문제가 발생하는 것을 알았을 때, onClose() -> onInit() 가 아니라 onInit() -> onClose() 순서로 호출되는 것이 문제라고 생각해서 가장 먼저 시도해 본 것은 toNamed(), offNamed()가 아닌 다른 method들을 이용해보는 것이었다.

설명만 보면 Get.offAndToNamed() 를 이용하면 될 것 같았다.

하지만, offAndToNamed()를 호출하면 아예 A2 Controller의 onInit()이 호출되지도 않는다.

[GETX] CLOSE TO ROUTE /ArticlePage/1
[GETX] GOING TO ROUTE /ArticlePage/2
I/flutter ( 9610): ### ArticlePageController 1 onClose()
[GETX] "ArticlePageController" onDelete() called
[GETX] "ArticlePageController" deleted from memory

(A1 -> A2 를 Get.offAndToNamed('/ArticlePage/2')로 이동한 경우의 로그)

 

이것은 분명 페이지 전환 과정에서 Controller 를 폐기하고 새로 생성할 때 기존 페이지의 Controller 와 새 페이지의 Controller를 제대로 구분하지 못해서라는 생각이 들었다.

이럴 때를 위해서 tag 이 있는 것이 아닌가?

 

class ArticlePage extends StatelessWidget {
  const ArticlePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    ArticlePageController controller =
        Get.put(ArticlePageController(), tag: Get.parameters['id']);  // article id 를 unique한 tag으로 사용했다.

    return Scaffold(
        appBar: AppBar(),
        body: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
          Text('This is ArticlePage ${controller.articleId}'),
          Obx(() => Text(
              'This Page is ' + (controller.isAlive.value ? 'alive' : 'dead'))),
          TextButton(
              onPressed: () =>
                  Get.offNamed('/ArticlePage/${controller.targetId}'),
              child: Text('Move To ArticlePage ${controller.targetId}')),
        ]));
  }
}

Get.put(ArticlePageController())할 때, unique한 tag(여기서는 article id)을 넣어줬더니 어머! 이제 기존 페이지의 Controller와 새 페이지의 Controller를 잘 구분해서 init, close 한다.

[GETX] Instance "GetMaterialController" has been created
[GETX] Instance "GetMaterialController" has been initialized
[GETX] GOING TO ROUTE /ArticlePage/1
I/flutter ( 9610): ### ArticlePageController 1 onInit()
[GETX] Instance "ArticlePageController" has been created with tag "1"
[GETX] Instance "ArticlePageController" with tag "1" has been initialized
[GETX] REPLACE ROUTE /ArticlePage/1
[GETX] NEW ROUTE /ArticlePage/2
I/flutter ( 9610): ### ArticlePageController 2 onInit()
[GETX] Instance "ArticlePageController" has been created with tag "2"
[GETX] Instance "ArticlePageController" with tag "2" has been initialized
I/flutter ( 9610): ### ArticlePageController 1 onClose()
[GETX] "ArticlePageController1" onDelete() called
[GETX] "ArticlePageController1" deleted from memory
[GETX] REPLACE ROUTE /ArticlePage/2
[GETX] NEW ROUTE /ArticlePage/1
I/flutter ( 9610): ### ArticlePageController 1 onInit()
[GETX] Instance "ArticlePageController" has been created with tag "1"
[GETX] Instance "ArticlePageController" with tag "1" has been initialized
I/flutter ( 9610): ### ArticlePageController 2 onClose()
[GETX] "ArticlePageController2" onDelete() called
[GETX] "ArticlePageController2" deleted from memory

A1 -> A2 이동 시(파란 색)에는 AC2 생성하고 AC1 삭제, A2 -> A1 이동 시(주황색)에는 AC1 생성하고 AC2 삭제가 의도한 대로 잘 된다.

 

tag을 이용하기 싫다면?

좀 더 복잡한 방법을 쓸 수 있다.

GetX 문서를 보면 Get.put() 으로 Dependency Injection한 경우에 삽입된 객체는 어디서나 access 할 수 있다. 즉, 전역 객체가 된다. (물론 삭제되기 전 까지)

 

그렇다면 ArticlePageController의 객체를 전역으로 만들지 않으면 해결되지 않겠는가?

안타깝게도 Get.put()에는 그런 옵션이 없지만, GetBuilder() 에는 global parameter가 있다.

 

class ArticlePage extends StatelessWidget {
  const ArticlePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetBuilder<ArticlePageController>(
      init: ArticlePageController(),
      global: false,
      dispose: (state) => state.controller?.onClose(),
      builder: (controller) {
        return Scaffold(
            appBar: AppBar(),
            body:
                Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
              Text('This is ArticlePage ${controller.articleId}'),
              Obx(() => Text('This Page is ' +
                  (controller.isAlive.value ? 'alive' : 'dead'))),
              TextButton(
                  onPressed: () =>
                      Get.offNamed('/ArticlePage/${controller.targetId}'),
                  child: Text('Move To ArticlePage ${controller.targetId}')),
            ]));
      },
    );
  }
}

// ArticlePageController는 동일
[GETX] Instance "GetMaterialController" has been created
[GETX] Instance "GetMaterialController" has been initialized
[GETX] GOING TO ROUTE /ArticlePage/1
I/flutter ( 9610): ### ArticlePageController 1 onInit()
[GETX] REPLACE ROUTE /ArticlePage/1
[GETX] NEW ROUTE /ArticlePage/2
I/flutter ( 9610): ### ArticlePageController 2 onInit()
I/flutter ( 9610): ### ArticlePageController 1 onClose()
[GETX] REPLACE ROUTE /ArticlePage/2
[GETX] NEW ROUTE /ArticlePage/1
I/flutter ( 9610): ### ArticlePageController 1 onInit()
I/flutter ( 9610): ### ArticlePageController 2 onClose()

Controller 인스턴스가 생성, 초기화, 정리, 삭제되었다는 GETX 로그가 빠지기는 했지만, 컨트롤러의 onInit(), onClose() 는 정상적으로 동작한다.

 

코드는 tag을 이용하는 쪽이 좀 더 간결해서 알아보기 편하지만, unique 한 tag을 지정하기 곤란한 경우에는 GetBuilder()를 사용하는 쪽도 무방하겠다. 참고로 나는 GetBuilder()를 사용했다.

 

끝.