Flutter GetX Get.back()或navigator.pop()会从内存中删除控制器,无法重新创建它。

16

我有两个页面:HomePageDetailsPage,以及关联的GetxControllers

HomePage:

class HomePage extends GetView<HomeController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('HomePage')),
      body: Container(
        child: Obx(
          () => ListView.builder(
            itemCount: controller.task.length,
            itemBuilder: (context, index) {
              return ListTile(
                leading: Text('${index + 1}'),
                title: Text(controller.task[index]["name"]),
                onTap: () {
                  Get.to(
                    DetailsPage(),
                    arguments: controller.task[index]["name"],
                  );
                },
              );
            },
          ),
        ),
      ),
    );
  }
}

HomeController:

class HomeController extends GetxController {
  final TaskRepository repository;
  HomeController({@required this.repository}) : assert(repository != null);

  final _task = [].obs;
  set task(value) => this._task.assignAll(value);
  get task => this._task;

  onInit() {
    super.onInit();
    getAllTask();
  }

  getAllTask() {
    repository.getAll().then((value) => task = value);
  }
}

正如您所看到的,HomeController 依赖于一个模拟存储库 TaskRepository

以及我的DetailsPage

class DetailsPage extends GetView<DetailsController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          GestureDetector(
            onTap: () {
              Get.back();
            },
            child: Row(
              children: [
                Icon(Icons.arrow_back),
                Text('Go Back'),
              ],
            ),
          ),
          Expanded(
            child: Center(
              child: Obx(
                () => Text(controller.taskDetail.value),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

DetailsController:

class DetailsController extends GetxController {
  final taskDetail = ''.obs;

  @override
  void onInit() {
    super.onInit();
    taskDetail.value = Get.arguments;
  }
}

我创建了一个AppDependencies类来初始化依赖项(控制器、存储库、API客户端等):

class AppDependencies {
  static Future<void> init() async {
    Get.lazyPut(() => HomeController(repository: Get.find()));
    Get.lazyPut(() => DetailsController());
    Get.lazyPut(() => TaskRepository(apiClient: Get.find()));
    Get.lazyPut(() => TaskClient());
  }
}

我会通过在main()中调用AppDependencies.init()来初始化所有依赖项:

void main() async {
  await AppDependencies.init();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

主页

详情页第一次

返回主页,再次进入详情页

正如第三张图片所示,从详情页返回主页,再次进入详情页会导致异常,提示:

"DetailsController" not found. You need to call "Get.put(DetailsController())" or "Get.lazyPut(()=>DetailsController())"

但我已经在main()中做过了。我还尝试使用Get.put()而不是Get.lazyPut(),但我发现对于Get.put(),任何其他依赖项的依赖关系必须在相关依赖项之前注册。例如,HomeController依赖于TaskRepository,因此如果像下面这样使用Get.put()

Get.put(TaskRepository());

Get.put(HomeController());

这不是我想要的,因为我不想手动跟踪每个元素的先后顺序。我发现这会导致如果有“返回”按钮(几乎每个页面都有),那么会出问题。

我做错了什么?

5个回答

9
如果您不想使用 fenix = true,您可以在click方法中像这样使用例如:
try {
   ///find the controller and 
   ///crush here if it's not initialized
   final authController = Get.find<AuthController>();

   if(authController.initialized)
     Get.toNamed('/auth');
   else {
     Get.lazyPut(() => AuthController());
     Get.toNamed('/auth');
   }

} catch(e) {

   Get.lazyPut(() => AuthController());
   Get.toNamed('/auth');
}

关于内存,需要考虑 fenix 参数:

[builder()] 的内部寄存器将保留在内存中,以便在使用 [Get.delete()] 删除实例后重新创建该实例。因此,对 [Get.find()] 的后续调用将返回相同的实例。


6

您需要将所有控制器绑定并加入到 GetMaterialApp 中。

您面临这个问题是因为当您返回时它会像这样删除或删除控制器:[GETX] "LoginController" onDelete() called

为了防止这种情况,您需要创建InitialBinding

InitialBinding

class InitialBinding implements Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => LoginController(LoginRepo()), fenix: true);
    Get.lazyPut(() => HomeController(HomeRepo()), fenix: true);
  }
}

在主方法中:
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Get.put(AppController());
    return GetMaterialApp(
      title: StringConst.APP_NAME,
      debugShowCheckedModeBanner: false,
      defaultTransition: Transition.rightToLeft,
      initialBinding: InitialBinding(),
      theme: ThemeData(
        primarySwatch: ColorConst.COLOR,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      initialRoute: RoutersConst.initialRoute,
      getPages: routes(),
    );
  }
}

谢谢


这会影响应用程序的性能吗?(如果我有4到6个控制器) - Asbah Riyas
fenix: true将禁止调用像onInit()和onReady()这样的生命周期方法,这在控制器的情况下并不是一个非常期望的行为。 - S. M. JAHANGIR
命名参数'fenix'未定义。 什么是fenix - mufazmi
请注意:fenix 只能与 Get.lazyPut() 一起使用,而不能与 Get.put() 一起使用。 - mufazmi

2

使用绑定和智能管理,您可以更好地控制控制器的初始化方式和时间。因此,如果您需要每次进入页面时都触发onInit,则可以使用绑定实现。为您的详细信息页面设置一个专用的绑定类。

class DetailsPageBinding extends Bindings {
  @override
  void dependencies() {
    // any controllers you need for this page you can lazy init here without setting fenix to true
  }
}

如果你还没有使用GetMaterialApp而是用的MaterialApp,那么你需要这样做。我建议在页面上添加static const id = 'details_page';,以便你不必处理原始字符串进行路由。

你的GetMaterialApp的一个基本示例如下所示。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      initialRoute: HomePage.id,
      title: 'Material App',
      getPages: [
        GetPage(name: HomePage.id, page: () => HomePage()),

// adding the new bindings class in the binding field below will link those controllers to the page and fire the dependancies override when you route to the page

        GetPage(name: DetailsPage.id, page: () => DetailsPage(), binding: DetailsPageBinding()),
      ],
    );
  }
}

那么你需要通过路由来进行编程。
Get.toNamed(DetailsPage.id)

原始回答:

在你的lazy init中添加fenix: true;查看lazyPut文档。

Get.lazyPut(() => HomeController(repository: Get.find()), fenix: true);

谢谢。我也尝试过那样做。但是这样控制器就会像单例一样行事,当返回页面时无法调用onInit()方法。 - S. M. JAHANGIR
谢谢!只是想知道,对于不特定于页面的依赖项(在这种情况下是存储库和客户端),它们可能会在不同的页面中使用。我需要在每个页面绑定中都懒加载它们吗?还是有更好的方法来消除这种重复?例如,我采取的上述方法,除了特定于页面的控制器之外?另一件事(可能与主题无关)是干净架构中的UseCases怎么样?我应该像带有fenix:true的单例一样全局注册它们吗?还是在页面绑定中注册它们? - S. M. JAHANGIR
不,你不需要为每个页面都这样做。你可以在全局范围内保留你的AppDependancies设置(或使用GetMaterialApp的initialBinding字段)。但是该页面的绑定应该解决你遇到的控制器被删除而无法重新创建的问题。使用我的示例,当你离开页面时它将被删除,然后在你返回时重新初始化。至于你的另一个问题,我会说这取决于具体情况和你的需求。有些情况下,你可能想要将控制器保留在内存中,但这超出了本讨论的范围。 - Loren.A
话虽如此,一般来说,如果您有仅需要用于一个页面的控制器,则对我而言使用绑定是有意义的,尽管这并非必需。GetX创建者关于绑定的良好解释在此处 - Loren.A
我遇到了同样的问题,只不过我正在使用嵌套导航,就像这个链接中所示 - https://flutter.dev/docs/cookbook/effects/nested-nav。因此,我无法使用getx导航器。是否仍有绑定的解决方法? - Hemabh

2

尝试

Navigator.pop(context);

替代
 Get.back();

0

一如既往,我又迟到了,但我希望这能帮助其他人。你可以使用类似于这样的东西:

Get.put(Dependency(), permanent: true);

使用 'permanent: true'

Get.put<Interface>(Dependency(), permanent: true);

使用接口使您可以根据需求拥有多种实现该接口的方式,并且您可以无缝地检索这些实现。
Get.find<Interface>();

希望这有所帮助。

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