getx icon indicating copy to clipboard operation
getx copied to clipboard

why the Controller instances not destory after call function `onClose()`

Open neopeng-bdy opened this issue 11 months ago • 6 comments

Describe the bug why the Controller instances not destory after call function onClose(). Or am I using the wrong posture?

**Reproduction code example:

import 'dart:async';

import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:pin_code_fields/pin_code_fields.dart';
import 'package:toocans/common/routes/route_manager.dart';
import 'package:toocans/common/utils/app_colors.dart';
import 'package:toocans/common/widgets/design/app_bar/design_app_bar.dart';
import 'package:toocans/common/widgets/design/button/design_button_match_width.dart';
import 'package:toocans/gen/lang_key.dart';

class FAGoogleCreateLogic extends GetxController {
  // QR码数据和密钥,实际应用中应从API获取
  final qrData = 'https://toocans.example.com/auth?code=123456789012345'.obs;
  final secretKey = '123123123123'.obs;

  // 验证码输入控制器
  late TextEditingController textEditingController;
  late StreamController<ErrorAnimationType> errorController;

  @override
  void onInit() {
    super.onInit();
    print('==========> FAGoogleCreateLogic onInit: ${identityHashCode(this)}');
    textEditingController = TextEditingController();
    errorController = StreamController<ErrorAnimationType>();
  }

  // 复制密钥到剪贴板
  void copySecretKey() {
    Clipboard.setData(ClipboardData(text: secretKey.value));
    Get.snackbar(
      LangKey.common.confirm.tr,
      LangKey.common.copySuccess.tr,
      backgroundColor: AppColors.toastBg,
      colorText: AppColors.textWhite,
      snackPosition: SnackPosition.BOTTOM,
    );
  }

  // 刷新数据,用于下拉刷新时调用
  Future<void> refreshData() async {
    // 这里可以实现刷新逻辑,比如重新获取二维码数据和密钥
    await Future.delayed(const Duration(milliseconds: 500));
    // 模拟更新数据,实际应用中可能需要调用API
    // qrData.value = '更新后的二维码数据';
    // secretKey.value = '更新后的密钥';
  }

  // 粘贴验证码
  void pasteCode() async {
    final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
    if (clipboardData != null && clipboardData.text != null) {
      final code = clipboardData.text!.replaceAll(RegExp(r'[^0-9]'), '');
      if (code.isNotEmpty) {
        // 取前6位或全部
        textEditingController.text =
            code.length > 6 ? code.substring(0, 6) : code;
      }
    }
  }

  // 确认按钮点击处理
  void onConfirmPressed() {
    if (textEditingController.text.length == 6) {
      // 实际应用中需验证输入的代码
      // 此处仅做示例,直接返回成功
      Get.back(result: true);
    } else {
      // 显示错误动画
      errorController.add(ErrorAnimationType.shake);
    }
  }

  @override
  void onClose() {
    print('==========> FAGoogleCreateLogic onClose: ${identityHashCode(this)}');
    textEditingController.dispose();
    errorController.close();
    super.onClose();
  }
}

// 创建一个绑定类
// class FAGoogleCreateBinding extends Bindings {
//   @override
//   void dependencies() {
//     print('==========> FAGoogleCreateBinding dependencies');
//     // 尝试删除已有实例
//     if (Get.isRegistered<FAGoogleCreateLogic>()) {
//       print('==========> 发现已注册的控制器,正在移除');
//       Get.delete<FAGoogleCreateLogic>(force: true);
//     }
//     // 使用Get.put替代Get.lazyPut
//     Get.put(FAGoogleCreateLogic(), permanent: false);
//   }
// }

class FAGoogleCreatePage extends StatelessWidget {
  static void launch() {
    RouteManager.toPage(() => FAGoogleCreatePage());
  }

  final logic = Get.put<FAGoogleCreateLogic>(FAGoogleCreateLogic());

  FAGoogleCreatePage({super.key});

  @override
  Widget build(BuildContext context) {
    // 添加日志查看控制器实例
    print('==========> FAGoogleCreatePage build 开始');
    final logic = Get.find<FAGoogleCreateLogic>();
    print('==========> Get.find 返回的控制器实例 controller hashCode: ${identityHashCode(logic)}');
    return Scaffold(
      backgroundColor: AppColors.background,
      appBar: DesignAppBar(
        title: LangKey.authenticator.faGoogleGuideTitle.tr,
      ),
      body: SafeArea(
        child: EasyRefresh(
          onRefresh: logic.refreshData,
          child: SingleChildScrollView(
            child: Padding(
              padding: EdgeInsets.symmetric(horizontal: 16.w),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  SizedBox(height: 16.h),
                  // 顶部提示框
                  Container(
                    padding:
                        EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.h),
                    decoration: BoxDecoration(
                      color: AppColors.colorBgTips,
                      borderRadius: BorderRadius.circular(10.r),
                    ),
                    child: Text(
                      LangKey.authenticator.faGoogleAuthenticatorTips.tr,
                      style: TextStyle(
                        fontSize: 12.sp,
                        color: AppColors.text222,
                      ),
                    ),
                  ),
                  SizedBox(height: 24.h),
                  // 二维码扫描说明
                  Text(
                    LangKey.authenticator.faGoogleAuthenticatorQrCodeTips.tr,
                    style: TextStyle(
                      fontSize: 14.sp,
                      color: AppColors.textThird,
                    ),
                  ),
                  SizedBox(height: 24.h),
                  // 二维码显示
                  Center(
                    child: Container(
                      width: 160.w,
                      height: 160.w,
                      padding: EdgeInsets.all(8.w),
                      decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(8.r),
                      ),
                      child: Obx(() {
                        return Image.network(
                          'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${logic.qrData.value}',
                          width: 160.w,
                          height: 160.w,
                          errorBuilder: (context, error, stackTrace) {
                            return Center(
                              child: Icon(
                                Icons.qr_code,
                                size: 100.w,
                                color: Colors.black,
                              ),
                            );
                          },
                        );
                      }),
                    ),
                  ),
                  SizedBox(height: 24.h),
                  // 密钥显示区域
                  Container(
                    padding: EdgeInsets.symmetric(horizontal: 16.w),
                    width: double.infinity,
                    height: 48.h,
                    decoration: BoxDecoration(
                      color: AppColors.color2B,
                      borderRadius: BorderRadius.circular(8.r),
                    ),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Obx(() => Text(
                              logic.secretKey.value,
                              style: TextStyle(
                                fontSize: 16.sp,
                                color: AppColors.textWhite,
                              ),
                            )),
                        GestureDetector(
                          onTap: logic.copySecretKey,
                          child: Icon(
                            Icons.copy,
                            color: AppColors.textWhite,
                            size: 20.w,
                          ),
                        ),
                      ],
                    ),
                  ),
                  SizedBox(height: 16.h),
                  // Authenticator app code 标签
                  Text(
                    LangKey
                        .authenticator.faGoogleAuthenticatorCodeVerifyTips.tr,
                    style: TextStyle(
                      fontSize: 14.sp,
                      color: AppColors.textPrimary,
                    ),
                  ),
                  SizedBox(height: 8.h),
                  // 验证码输入框 - 使用PinCodeTextField替代
                  PinCodeTextField(
                    appContext: context,
                    length: 6,
                    animationType: AnimationType.fade,
                    pinTheme: PinTheme(
                      shape: PinCodeFieldShape.box,
                      borderRadius: BorderRadius.circular(8.r),
                      fieldHeight: 62.h,
                      fieldWidth: 46.w,
                      activeFillColor: AppColors.editBg,
                      inactiveFillColor: AppColors.editBg,
                      selectedFillColor: AppColors.editBg,
                      activeColor: AppColors.textSecondary,
                      inactiveColor: Colors.transparent,
                      selectedColor: AppColors.textSecondary,
                      borderWidth: 1.w,
                      activeBorderWidth: 1.w,
                      selectedBorderWidth: 1.w,
                      inactiveBorderWidth: 1.w,
                      errorBorderColor: AppColors.error,
                      errorBorderWidth: 1.w,
                    ),
                    cursorColor: AppColors.brand,
                    animationDuration: const Duration(milliseconds: 300),
                    enableActiveFill: true,
                    keyboardType: TextInputType.number,
                    controller: logic.textEditingController,
                    errorAnimationController: logic.errorController,
                    onCompleted: (value) {
                      // 验证码输入完成的回调,可以自动触发确认
                      // logic.onConfirmPressed();
                    },
                    onChanged: (value) {
                      // 输入变化的回调
                    },
                    beforeTextPaste: (text) {
                      // 粘贴前的回调,返回 true 允许粘贴
                      return true;
                    },
                    textStyle: TextStyle(
                      fontSize: 24.sp,
                      color: AppColors.textPrimary,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  // Paste 按钮
                  GestureDetector(
                    onTap: logic.pasteCode,
                    child: Text(
                      LangKey.common.paste.tr,
                      style: TextStyle(
                        color: AppColors.textLink,
                        fontSize: 12.sp,
                      ),
                    ),
                  ),
                  SizedBox(height: 36.h),
                ],
              ),
            ),
          ),
        ),
      ),
      bottomNavigationBar: SafeArea(
        child: Padding(
          padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h),
          child: DesignButtonMatchWidth(
            text: LangKey.common.confirm.tr,
            onPressed: logic.onConfirmPressed,
          ),
        ),
      ),
    );
  }
}

To Reproduce Steps to reproduce the behavior:

  1. Go to 'FAGoogleCreatePage'
  2. Click on 'back' the Controller instances call onClose() function
  3. Second time go to 'FAGoogleCreatePage'
  4. the Controller instances still in and the onInit() function not call

Expected behavior the Controller instances call onClose() function and destory

Screenshots

Image

Flutter Version: 3.29.1

Getx Version: get: ^4.7.2

Describe on which device you found the bug: ex: all the device also in ios simulator

neopeng-bdy avatar Mar 11 '25 08:03 neopeng-bdy

new discovery, when I use Get.create, it performance good, the controller destory when call onClose().

  final logic =
      Get.create<FAGoogleCreateLogic>(() => FAGoogleCreateLogic(), permanent: false);

neopeng-bdy avatar Mar 11 '25 09:03 neopeng-bdy

put the controller inside binding or put the controller inside init state on GetBuilder

nandakista avatar Mar 16 '25 04:03 nandakista

put the controller inside binding or put the controller inside init state on GetBuilder

I tried put the controller inside binding and it's not work.

waiting for you GetX version 5, I will test my code on it.

for currently, I used Get.create to resolve this issue.

neopeng-bdy avatar Mar 18 '25 03:03 neopeng-bdy

Use GetBuilder

ZhuJHua avatar Mar 20 '25 02:03 ZhuJHua

Use GetBuilder

thks, I will try it later

neopeng-bdy avatar Mar 20 '25 02:03 neopeng-bdy

老哥,你这样跳转就会自动释放掉的。Get.to(() => FAGoogleCreatePage()),()=> 不能少。

winter-tech avatar Apr 01 '25 02:04 winter-tech