如何让SongDetailsBottomsheetController释放并在每次打开BottomSheet时重新初始化?

如何让SongDetailsBottomsheetController释放并在每次打开BottomSheet时重新初始化

class SongDetailsBottomsheet extends StatelessWidget {
  final SongDetailsBottomsheetController controller;
  SongDetailsBottomsheet({super.key, required MediaItem track})
      : controller = Get.put(SongDetailsBottomsheetController(track: track));

  @override
  Widget build(BuildContext context) {
    return const SizedBox(
            height: 220,
            child: Column(
              children: [
                SizedBox(width: 100,height: 100,)
              ],
            ),
          );
  }
}
class SongDetailsBottomsheetController extends GetxController {
  // bool isLoading = true;
  // late String avatarUrl;
  final MediaItem track;
  SongDetailsBottomsheetController({required this.track});

  @override
  void onInit() {
    super.onInit();
    debugPrint('start');
  }

  @override
  void onReady() {
    super.onReady();
    debugPrint('ready');
    // getArtistAvatar();
  }
  @override
  onClose(){
    super.onClose();
    debugPrint('closing');

  }
       onPressed: () {
            showModalBottomSheet(
                context: context,
                builder: (context) {
                  return SongDetailsBottomsheet(track: track);
                });
}

当按下按钮时,弹出BottomSheet,但关闭时不会打印"closing",当再次打开时,也不会重新调用OnInit()和OnReady(),每次传入的MediaItem都不同,但SongDetailsBottomsheetController中的MediaItem不变

调试信息:
I/flutter (25437): start
I/flutter (25437): ready
W/WindowOnBackDispatcher(25437): OnBackInvokedCallback is not enabled for the application.
W/WindowOnBackDispatcher(25437): Set 'android:enableOnBackInvokedCallback="true"' in the application manifest.
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
W/WindowOnBackDispatcher(25437): OnBackInvokedCallback is not enabled for the application.
W/WindowOnBackDispatcher(25437): Set 'android:enableOnBackInvokedCallback="true"' in the application manifest.
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
W/WindowOnBackDispatcher(25437): OnBackInvokedCallback is not enabled for the application.
W/WindowOnBackDispatcher(25437): Set 'android:enableOnBackInvokedCallback="true"' in the application manifest.
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0
D/SystemUtils(25437): [isSystemApp] begin android.app.Application@da53904
D/ScreenUtils(25437): isMultiWindow:0,userId:0
D/ScreenUtils(25437): hasVivoFreeformTasks:0,userId:0
D/ScreenUtils(25437): isLittleVExist:0,userId:0
D/SystemUtils(25437): userId:0

尝试使用Get.bottomsheet(),但问题依然存在

阅读 1.9k
avatarAI BotBETA

在 GetX 框架中,当你使用 Get.put() 来创建并存储一个控制器时,这个控制器实例会被存储在 GetX 的依赖注入容器中,直到它被显式地从容器中移除或应用程序的生命周期结束。这就是为什么你在多次打开和关闭 BottomSheet 时,SongDetailsBottomsheetControlleronInit()onReady() 方法没有被重新调用,并且 onClose() 方法也没有被调用的原因:因为同一个控制器实例被复用了。

要解决这个问题,你可以在每次需要显示 BottomSheet 时创建一个新的 SongDetailsBottomsheetController 实例,而不是从依赖注入容器中获取它。这里有一种方法来实现这一点:

  1. 移除 Get.put() 的使用:在你的 SongDetailsBottomsheet 构造函数中,不要使用 Get.put() 来创建控制器实例。
  2. 直接在构造函数中创建控制器实例:将控制器作为局部变量或作为 SongDetailsBottomsheet 类的成员变量(但不存储在 GetX 的依赖注入容器中)。
  3. 在 BottomSheet 关闭时手动处理资源释放:由于 showModalBottomSheet 不直接支持关闭回调,你可能需要在 BottomSheet 内部添加一个按钮或逻辑来触发控制器的清理或重新初始化。

下面是一个修改后的示例,展示了如何在每次打开 BottomSheet 时创建一个新的控制器实例:

class SongDetailsBottomsheet extends StatelessWidget {
  final MediaItem track;
  late final SongDetailsBottomsheetController controller;

  SongDetailsBottomsheet({super.key, required this.track}) {
    // 在构造函数中直接创建控制器实例
    controller = SongDetailsBottomsheetController(track: track);
    // 你可以在这里监听控制器的 onClose 或其他生命周期事件
    // 但请注意,BottomSheet 没有直接的方法来检测关闭事件
    // 除非你添加了一个按钮或逻辑来手动触发它
  }

  @override
  Widget build(BuildContext context) {
    // 使用 controller 来进行 UI 更新或逻辑处理
    return SizedBox(
      height: 220,
      child: Column(
        children: [
          // 你的 UI 组件
          Text('Track Title: ${controller.track.title}'), // 假设 MediaItem 有一个 title 属性
        ],
      ),
    );
  }

  @override
  void dispose() {
    // 当 Widget 被销毁时,你可以选择在这里释放控制器资源
    // 但在这个案例中,由于每次打开都会创建一个新的实例,所以可能不需要
    // 除非你在控制器中分配了需要手动释放的资源
    super.dispose();
  }
}

class SongDetailsBottomsheetController {
  final MediaItem track;

  SongDetailsBottomsheetController({required this.track});

  // 你可以在这里添加你的初始化逻辑
  void init() {
    // 初始化代码
    debugPrint('Initialized with track: ${track.title}'); // 假设 MediaItem 有一个 title 属性
  }

  // 你可以在这里添加其他需要的方法
}

// 显示 BottomSheet 的代码保持不变,但每次都会创建一个新的 SongDetailsBottomsheet 实例

请注意,由于 showModalBottomSheet 不提供直接的关闭回调,因此你可能需要在 BottomSheet 内部添加逻辑来触发控制器的某些清理操作。如果你确实需要检测 BottomSheet 的关闭事件,你可以考虑在 BottomSheet 内部添加一个按钮或使用其他 UI 组件来允许用户关闭它,并在该操作触发时执行必要的清理。

另外,请注意,我在上面的示例中移除了对 GetX 依赖注入的使用,因为在这个特定的情况下,每次都需要一个新的控制器实例。如果你仍然想使用 GetX 的功能,你可能需要寻找其他方式来管理控制器的生命周期,而不是简单地使用 Get.put()

2 个回答

方法1:

这是基于你的代码进行了一些修改

class SongDetailsBottomsheet extends StatelessWidget {
  late SongDetailsBottomsheetController controller;

  SongDetailsBottomsheet({super.key, required MediaItem track})
      : controller = Get.put(SongDetailsBottomsheetController(track: track));

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 220,
      child: Column(
        children: [
          Center(child: Text(controller.track.flag)),
          SizedBox(
            width: 100,
            height: 100,
          ),
          Obx(
            () {
              return TextButton(
                  onPressed: () {
                    controller.touchNumber.value++;
                  },
                  child: Text(controller.touchNumber.value.toString()));
            },
          )
        ],
      ),
    );
  }
}

class MediaItem {
  MediaItem({required this.flag});

  final String flag;
}

class SongDetailsBottomsheetController extends GetxController {
  // bool isLoading = true;
  // late String avatarUrl;
  var touchNumber = 0.obs;
  late MediaItem track;

  SongDetailsBottomsheetController({required this.track});

  @override
  void onInit() {
    super.onInit();
    debugPrint('start');
  }

  @override
  void onReady() {
    super.onReady();
    debugPrint('ready');
    // getArtistAvatar();
  }

  @override
  onClose() {
    super.onClose();
    debugPrint('closing');
  }
}

解决方法:

MediaItem track = MediaItem(flag: Random().nextInt(100).toString());
        await showModalBottomSheet(
          context: Get.context!,
          builder: (context) {
            return SongDetailsBottomsheet(track: track);
          },
        );
        Get.delete<SongDetailsBottomsheetController>();
  • Get.put(SongDetailsBottomsheetController(track: track)) 尽管这个方法会调用多次但是SongDetailsBottomsheetController实例会被创建一次;所以 onInit,onReady 只有在第一次被创建的时候调用一次
  • Get.put(SongDetailsBottomsheetController(track: track)) 方法会创建SongDetailsBottomsheetController这个实例并放在当前currentRoute的下面; 并且这个实例会被复用;当多次调用 Get.find<SongDetailsBottomsheetController>(); 会返回已经创建的这个实例;
    比如:我有一个HomepPage 而 HomepPage 是通过GetPage(name:'/homePage',page:HomePage)方法声在Getx的route中;当我在HomepPage下面的某个子组件中调用 showModalBottomSheet方法;其实 SongDetailsBottomsheetController 是被关联到 /homePage 这个路由下的;所以在HomePage这个界面释放的时候,SongDetailsBottomsheetController才被释放这个时候onClose 方法才会调用;所以你代码中onClose是不会执行的
  • 因为 Get.put(SongDetailsBottomsheetController(track: track)) 会被复用所以使用Get.delte进行删除

方法2 使用StatefulWidget组件与GetController组合

MediaItem track = MediaItem(flag: Random().nextInt(100).toString());
        showModalBottomSheet(
          context: Get.context!,
          builder: (context) {
            return SongDetailsBottomsheet(track: track);
          },
        );

class SongDetailsBottomsheet extends StatefulWidget {
  final SongDetailsBottomsheetController controller;

  SongDetailsBottomsheet({super.key, required MediaItem track})
      : controller = SongDetailsBottomsheetController(track: track);

  @override
  State<SongDetailsBottomsheet> createState() => _SongDetailsBottomsheetState();
}

class _SongDetailsBottomsheetState extends State<SongDetailsBottomsheet> {
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    SongDetailsBottomsheetController controller = widget.controller;

    return SizedBox(
      height: 220,
      child: Column(
        children: [
          Center(child: Text(controller.track.flag)),
          SizedBox(
            width: 100,
            height: 100,
          ),
          Obx(
            () {
              return TextButton(
                  onPressed: () {
                    controller.touchNumber.value++;
                  },
                  child: Text(controller.touchNumber.value.toString()));
            },
          )
        ],
      ),
    );
  }
}

class MediaItem {
  MediaItem({required this.flag});

  final String flag;
}

class SongDetailsBottomsheetController extends GetxController {
  // bool isLoading = true;
  // late String avatarUrl;
  var touchNumber = 0.obs;
  late MediaItem track;

  SongDetailsBottomsheetController({required this.track});
}
  • 换成 StatefulWidget 类型组件考虑到要使用你可能会使用 initState,dispose 这些方法
  • 使用GetController 你可以方便进行变量状态的更新

方法3 使用 StatefulWidget 与 ValueNotifier

class SongDetailsBottomsheet extends StatefulWidget {
  final MediaItem track;

  const SongDetailsBottomsheet({super.key, required this.track});

  @override
  State<SongDetailsBottomsheet> createState() => _SongDetailsBottomsheetState();
}

class _SongDetailsBottomsheetState extends State<SongDetailsBottomsheet> {
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final vn = ValueNotifier<int>(0);

    return SizedBox(
      height: 220,
      child: Column(
        children: [
          Center(child: Text(widget.track.flag)),
          SizedBox(
            width: 100,
            height: 100,
          ),
          TextButton(
            onPressed: () {
              vn.value++;
            },
            child: ListenableBuilder(
              listenable: vn,
              builder: (context, child) {
                return Text(vn.value.toString());
              },
            ),
            // child: ValueListenableBuilder(
            //   valueListenable: vn,
            //   builder: (context, value, child) {
            //     return Text(vn.value.toString());
            //   },
            // ),
          )
        ],
      ),
    );
  }
}

class MediaItem {
  MediaItem({required this.flag});

  final String flag;
}

使用SongDetailsBottomsheetController()创建SongDetailsBottomsheetController,则每次都会创建新的SongDetailsBottomsheetController并初始化

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题