본문 바로가기

일::개발

Flutter: getx routing to same page with different data. (controller 중복으로 만들기 feat. tag)

다른 데이터를 가진 같은 페이지로 이동할 때의 문제다.

product id 를 파라미터로 가지는 ProductScreen 페이지 간 이동하는 간단한 예를 보면 

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:route_sample/app_pages.dart';
import 'dart:math';

class ProductScreen extends GetView<ProductScreenController> {
  const ProductScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    Get.put(ProductScreenController());
    var id = Random().nextInt(10000);
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: [
          Obx(() => Text('Product Id: ${controller.productId}')),
          TextButton(
            child: Text('to Product $id'),
            onPressed: () =>
                Get.toNamed(Routes.PRODUCT + '/$id'),
          ),
        ],
      ),
    );
  }
}

class ProductScreenController extends GetxController {
  final productId = ''.obs;

  @override
  void onInit() {
    super.onInit();
    productId.value = Get.parameters['id'] ?? '';
    debugPrint('ProductScreenController.onInit(), $hashCode');
  }

  @override
  void onClose() {
    debugPrint('ProductScreenController.onClose(), $hashCode');
    super.onClose();
  }
}

"/product/{임의의 product_id}" 로 이동하는 간단한 예시인데, 원하는 것은 ProductScreenController의 onInit() 에서 파라미터로 받은 product id 에 해당하는 데이터를 로드하는 것이었다.

 

그러나 실행해보면 아래 로그처럼 새 ProductScreen 으로 이동하지만 controller의 onInit()는 실행되지 않고, 당연히 1행의 Product Id 도 변경되지 않는다.

[GETX] Instance "ProductScreenController" has been initialized
[GETX] GOING TO ROUTE /product/6437
[GETX] GOING TO ROUTE /product/6833
[GETX] GOING TO ROUTE /product/6716
[GETX] GOING TO ROUTE /product/272
[GETX] GOING TO ROUTE /product/3518

실제 라우팅 히스토리 스택에도 

/home==>/product/empty==>/product/6437==>/product/6833==>/product/6716==>/product/272==>/product/3518

이렇게 모든 경로가 잘 저장되어 있고, back() 하면 하나씩 뒤로 이동하지만, 역시 onClose()는 실행되지 않는다.

GetX는 내부적으로 객체를 find()할 때 이미 존재하는 객체인지 그 key로 확인하고 기존의 객체를 반환하거나 새로 생성하게 되는데, 이 때 사용하는 key는 클래스 이름과 tag을 이용하여 만든다. 위의 예에서는 tag를 사용하지 않았기 때문에, Controller객체의 key가 클래스명("ProductScreenController")이 되고, 같은 이름을 가진 Controller가 이미 존재하기 때문에 다시 생성하지 않고 기존의 것을 재사용하게 되는 것이다.

 

위에서 말했듯이 GetX는 find() 에서 Controller를 초기화하기 때문에, 만약 Obx(() => Text('Product Id: ${controller.productId}')), 라인을 주석처리해서 controller를 사용하지 않으면 페이지 이동을 해도 controller 를 다시 생성하지 않는다.

 

그렇다면 tag를 넣어주면 될 것 같은데, 한번 해보자.

class ProductScreen extends GetView<ProductScreenController> {
  ProductScreen(this.productId, {Key? key})
      : super(key: key); // GetView.controller를 사용하기 위해 productId 를 생성자에 추가했다.

  String? productId;

  @override
  String? get tag =>
      productId; // tag이 있는 controller를 만들었기 때문에 여기서 tag 을 override 해주지 않으면 GetView.controller를 사용하지 못한다.

  @override
  Widget build(BuildContext context) {
    Get.put(ProductScreenController(productId), tag: productId);

    var newProductId = Random().nextInt(10000).toString(); // 임의로 만든 다음 프로덕트 ID

    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: [
          Text('Product Id: ${controller.productId}, ${controller.hashCode}'),
          TextButton(
            child: Text('to Product $newProductId'),
            onPressed: () => Get.toNamed(Routes.PRODUCT + '/$newProductId'),
          ),
        ],
      ),
    );
  }
}

class ProductScreenController extends GetxController {
  ProductScreenController(this.productId);
  final String?
      productId; // Controller에서 초기화를 위해 필요한 productId를 생성자로 전달받는다.

  @override
  void onInit() {
    super.onInit();
    print('GETX ProductScreenController.onInit(), $productId, $hashCode');
  }

  @override
  void onClose() {
    print('GETX ProductScreenController.onClose(), $productId, $hashCode');
    super.onClose();
  }
}


abstract class AppPages {
  static final pages = [
    GetPage(
      name: Routes.PRODUCTS,
      page: () => ProductScreen(Get.parameters['id'] ?? ''),	// 파라미터로 전달된 Product id를 View에 넘긴다.
      ),
  ];
}

이렇게 하면 매번 onInit(), onClose()가 잘 불린다.

다음에는 GetBuilder()를 이용하면 좀 더 깔끔해질 지 한번 알아보도록 하자.