GetX 控制器未自动处理销毁

11
我有一个在Android上只使用GetX作为状态管理库的极简示例应用正在运行。它有两个屏幕:LandingPage和MainScreen。从MainScreen返回到LandingPage屏幕时,控制器没有按预期自动释放。 我仅使用Flutter的导航而没有使用GetMaterialApp进行封装。
我的期望是,在实例化控制器时,由控制器公开的值应该重置为其初始值。 然而,小部件继续显示来自控制器的最后一个值。
我正在使用Flutter和GetX的最新版本:分别为2.2.3和4.3.8
感谢您的帮助。
代码:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
   
    primarySwatch: Colors.purple,
  ),
  home: LandingScreen(),
  );
 }
} 

class LandingScreen extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
  return Container(
   color: Colors.blue[800],
   child: Center(
     child: ElevatedButton(
       onPressed: () => {
         Get.to(MainScreen())
       },
       child: const Text('Navigate to Second Screen'),
     ),
    ),
  );
 }
}

class MainScreen extends StatelessWidget {
 final MyController controller = Get.put(MyController());

 @override
 Widget build(BuildContext context) {
  return Scaffold(
  body: SafeArea(
    child: Container(
      color: Colors.blueAccent,
      child: Center(
        child: Column(
          children: [
            Obx(() => Text('Clicked ${controller.count}')),
            FloatingActionButton(
              onPressed: controller.increment,
              child: Text('+'),
            ),
            ElevatedButton(
              onPressed: ()=>{Navigator.of(context).pop()},
              child: Text('Go Back'),
            )
          ],
          ),
         ),
        ),
       ),
      );
     }
    }

  class MyController extends GetxController {

   var count = 0.obs;
   void increment() => count++;

  }

1
是的,控制器在使用GetX导航之前不会被释放。 - Mohammed Alfateh
好的。我会尝试使用GetX导航并更新。不过文档中有提到吗? - Akash Gorai
1
我遇到了类似的问题,但在使用GetX导航后,dispose工作良好。可以分享代码吗? - Mohammed Alfateh
@7mada,你能提一下你正在使用的getx包版本吗? - Akash Gorai
1
好的,所以我学到了必须传递一个返回该小部件而不是小部件本身的回调。现在使用GetMaterialApp很好用。 - Akash Gorai
显示剩余2条评论
7个回答

14

自动处理

要在小部件被移除时处理GetxController的处理方式,请在build()方法中使用Get.put而不是作为一个字段。这是根据GetX的维护者正确的使用方式

然后控制器可以在MainScreen弹出时被处理。

错误用法

控制器将不会被处理:

class MainScreen extends StatelessWidget {
 final MyController controller = Get.put(MyController());

 @override
 Widget build(BuildContext context) {
  return Scaffold(

Get.put(...) 不应该作为一个 field 来进行实例化和注册。

否则,控制器的注册将附加到上面的 widget(即问题中的 LandingScreen),而不是 MainScreen。而且只有当 LandingScreen 被销毁时,MyController 才会被销毁。(在问题的代码中,LandingScreen 是“主屏幕”小部件,“LandingScreen”的销毁仅在应用退出时发生。)

为什么这样做?

如果将控制器实例化为字段,GetX 会使用当前可用的上下文——父级小部件的上下文——而不是实例化小部件的上下文来记录控制器的创建。实例化小部件的上下文直到其 build() 方法才可用。小部件类的实例化和其 build() 方法的运行是两个不同的事情。 build() 方法并不是小部件本身的工厂构造函数,而是 Flutter 框架在小部件实例化后使用的生命周期方法,用于填充小部件并将其作为 Element 插入 / 挂载在渲染对象的元素树中。(这是我目前的理解。)

在问题的示例中,MainScreen 小部件在 LandingScreen 的上下文中实例化。MainScreen 及其字段都在 LandingScreen 的上下文中。MainScreen's build() 方法在其实例化后运行。在小部件的实例化过程中,MainScreen's context 是不可用的。当 MainScreen's build()方法运行时可以使用该上下文。

为了让 MyControllerMainScreen 被销毁时也被删除 / 销毁,我们必须在 MainScreen 上下文中实例化 MyController,该上下文在调用它的 build(BuildContext context) 方法时可用。这是我们应该将其与 GetxController 关联起来以便 GetX 自动处理其销毁的上下文。

正确使用方式

class MainScreen extends StatelessWidget {

 @override
 Widget build(BuildContext context) {
  // ↓ instantiate/register here inside build ↓
  final MyController controller = Get.put(MyController());
  return Scaffold(

现在,当MainScreen从路由堆栈中弹出时,MyController将被GetX清理/释放(我假设GetX观察路由历史记录以知道何时进行清理)。


如果我因为某种原因正在使用StatefulWidget怎么办?将其放在build方法中可能并不是一个好主意。 - Mena
@Mena 嗯...,乍一看,我想不出为什么把它放到 StatefulWidgetbuild() 方法中会有什么问题。但是,在 StatefulWidgetState 对象中注册控制器可能更有意义。当 State 对象被销毁时,控制器也将被销毁。 - Baker
尽管我在build方法中调用了Get.put,但我并没有观察到这种行为。请注意,我使用的是Navigator.of(context).pushNamedAndRemoveUntil而不是Navigator.pop(context) - ASAD HAMEED
非常感谢。我是在构建方法之外初始化它,这导致在屏幕弹出后无法处理它。 - Usama Javed
这是错误的!在build方法中放置控制器并不需要处理控制器的释放。 - Akash Gorai
显示剩余2条评论

13

您需要先使用GetX导航。 然而,在撰写本答案时,存在一个错误会导致控制器无法自动清除。因此,建议现在使用绑定或从StatefulWidget手动处理它们,直到错误得到修复。

@override
  void dispose() {
    Get.delete<Controller>();
    super.dispose();
  }

2021年11月22日更新 您仍可以使用上述解决方案,或者更加优雅的方法是结合Bindings和扩展GetView<YourController>来代替Stateful/Stateless Widgets。GetXController提供了大部分你从StatefulWidget中所需要的功能,同时你也不一定需要使用GetX Navigation。

使用此方法时,嵌套导航同样适用。

要使用Bindings,这里有多种方法进行说明:这里


1
这是一个很好的答案,当我们进入另一个屏幕时,控制器将关闭,当我们返回时它将重新加载。 - Kavindu Dissanayake
你不一定需要这样做。如果需要手动处理事物,使用getx没有任何优势。 - Akash Gorai

5
你可以像下面这样进行延迟初始化控制器:
late final YourController controller;

在您的initState函数内部:

@override
void initState() {
    super.initState();

    Get.delete<YourController>();
    controller = Get.put(YourController());
}

这将解决你的问题。


这个有效了!但我想知道它是如何工作的。 - 钟智强
@JohnMelodyMe 只是删除然后再放置控制器。 - Jetwiz

0

当我在controller.dart文件中定义了以下方法时,它对我有些用:

late Readcontroller controller;

void dispose(){ Get.delete(); controller = Get.put(ReadController()); }

然后在我的主文件reading.dart中:

widget build(Build context){ final ReadController controller = Get.put(ReadController()); }

上面的代码对我来说运行得很好

试一试吧……

}


你的回答可以通过添加更多支持信息来改进。请[编辑]以添加更多详细信息,例如引用或文档,以便其他人可以确认你的答案是否正确。你可以在帮助中心找到有关如何撰写良好答案的更多信息。 - mohammad mobasher

0

如果您在getMaterialApp上添加路由,则无需在build内初始化控制器:

添加“initialRoute”和“getPages”属性,并删除home属性:

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.purple,
  ),
  initialRoute: '/landing',
  getPages: [
    GetPage(name: '/landing', page: () => LandingScreen()),
    GetPage(name: '/main', page: () => MainScreen()),
  ],
  );
 }
}

在 LandingPage() 中将 Get.to() 更改为 Get.toNamed():
class LandingScreen extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
  return Container(
   color: Colors.blue[800],
   child: Center(
     child: ElevatedButton(
       onPressed: () => Get.toNamed('/main'),
       child: const Text('Navigate to Second Screen'),
     ),
    ),
  );
 }
}

在主屏幕上更改导航器弹出为 Get.back():

class MainScreen extends StatelessWidget {
 final MyController controller = Get.put(MyController());

 @override
 Widget build(BuildContext context) {
  return Scaffold(
  body: SafeArea(
    child: Container(
      color: Colors.blueAccent,
      child: Center(
        child: Column(
          children: [
            Obx(() => Text('Clicked ${controller.count}')),
            FloatingActionButton(
              onPressed: controller.increment,
              child: Text('+'),
            ),
            ElevatedButton(
              onPressed: ()=>Get.back(),
              child: Text('Go Back'),
            )
          ],
          ),
         ),
        ),
       ),
      );
     }
    }

0
在不同的文件中定义绑定类,不要使用公共文件来定义绑定。

0

你需要使用 getPages 和 navigation getx 方式

 Get.to(() => const NotificationsScreen())

路由:

GetMaterialApp(
          getPages: NavigatorService.onGenerateRoute(),

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接