Flutter GetX Get.back () или navigator.pop () удаляет контроллер из памяти и не может его воссоздать

У меня две страницы: HomePage и DetailsPage и связанные с ними GetxControllers.

Домашняя страница:

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());
  }
}

Я инициализирую все зависимости, вызывая AppDependencies.init() на main():

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(),
    );
  }
}

Домашняя страница

Подробная страница в первый раз

Вернуться на главную страницу, а затем снова перейти на страницу сведений

Как видно на третьем изображении, при возврате с DetailsPage к HomePage и возврата к DetailsPage возникает исключение, в котором говорится:

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

Но я уже делал это на main(). Я также пробовал использовать Get.put() вместо Get.lazyPut(), но обнаружил, что для Get.put() любые зависимости любой другой зависимости должны быть зарегистрированы перед зависимой. Например, HomeController зависит от TaskRepository, поэтому TaskRepository должен быть перед HomeController, если используется Get.put(), например:

Get.put(TaskRepository());

Get.put(HomeController());

И это не то, что я хочу, потому что я не хочу отслеживать то, что происходит раньше, вручную. И я обнаружил, что это вызывает наличие кнопки «Назад» (которая есть почти на каждой странице).

Что я здесь делаю не так?


person S. M. JAHANGIR    schedule 10.02.2021    source источник


Ответы (3)


Если вы не хотите использовать fenix = true, вы можете использовать что-то вроде этого, например, в своем методе щелчка:

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 ()] вернут тот же экземпляр.

person Álvaro Agüero    schedule 09.06.2021

Обновленный ответ с привязками:

Вы можете добиться большего контроля над тем, как и когда вы инициализируете контроллеры с помощью привязок и интеллектуального управления. Поэтому, если вам нужно, чтобы 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)

Оригинальный ответ:

Добавьте fenix: true в свой ленивый init; Проверьте документацию на lazyPut.

Get.lazyPut(() => HomeController(repository: Get.find()), fenix: true);
person Loren.A    schedule 10.02.2021
comment
Спасибо. Я тоже пробовал. Но тогда он (контроллер) ведет себя как синглтон и не может вызывать onInit () при переходе обратно на страницу. - person S. M. JAHANGIR; 11.02.2021
comment
Попался, см. Обновленный ответ. - person Loren.A; 12.02.2021
comment
Спасибо! Просто чтобы знать, как насчет зависимостей, не зависящих от страницы (в данном случае репозитория и клиента), которые могут использоваться на разных страницах. Мне нужно lazyPut их в каждой привязке страницы? Или есть способ лучше избавиться от этого повторения? Например, вышеупомянутый подход, который я использую, за исключением контроллеров для конкретных страниц? Другое дело (может быть, не по теме), а как насчет вариантов использования в чистой архитектуре? Должен ли я регистрировать их глобально, как синглтон, с помощью fenix: true? Или прописать их на страницах привязки? - person S. M. JAHANGIR; 12.02.2021
comment
Нет, вам не нужно делать это для каждой страницы. Вы можете оставить настройку AppDependancies для глобальных вещей (или использовать поле initialBinding в GetMaterialApp). Но привязки для этой страницы должны решить проблему с удалением, а не воссозданием контроллера. В моем примере он будет удален, когда вы покинете страницу, а затем будет повторно инициализирован, когда вы вернетесь к ней. Что касается вашего другого вопроса, я бы сказал, что это индивидуально в зависимости от ваших потребностей. Существуют сценарии, в которых вы можете захотеть сохранить контроллер в памяти, но это выходит за рамки этого обсуждения. - person Loren.A; 12.02.2021
comment
При этом, вообще говоря, если у вас есть контроллеры, которые нужны только для одной страницы, то для меня это заставляет использовать привязки, хотя это ни в коем случае не является требованием. Хорошее объяснение привязок от создателя GetX, здесь - person Loren.A; 12.02.2021

Вам нужно привязать весь контроллер и добавить в GetMaterialApp.

Вы столкнулись с этой проблемой из-за того, что в то время вы использовали его для удаления или удаления контроллера, например: [GETX] LoginController onDelete (), вызываемый

Чтобы предотвратить эту проблему, вам необходимо создать 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(),
    );
  }
}

Спасибо

person webaddicted    schedule 30.03.2021
comment
Повлияет ли это на производительность приложения (если бы у меня было от 4 до 6 контроллеров) - person Asbah Riyas; 15.04.2021
comment
fenix: true запретит вызов методов жизненного цикла, таких как onInit () и onReady (), что не очень ожидаемое поведение в случае контроллеров. - person S. M. JAHANGIR; 18.04.2021