Создание пользовательского контроллера в Flutter

#flutter #flutter-hooks

#flutter #flutter-крючки

Вопрос:

Я создал виджет с именем InfiniteScroll , который обрабатывает асинхронно загруженные данные и отображает их ListView.builder . Однако у меня возникли проблемы с созданием контроллера для него (например, для очистки всех загруженных данных). Я прочитал реализацию существующих контроллеров, таких как TextEditingController , но, похоже, я не могу понять это. Вот пример того, чего я пытаюсь достичь:

 // I have
InfiniteScroll(
  fetchMore: () async {}, // fetching more data
  builder: (data) {}, // building an item
)

// need
InfiniteScroll(
  controller: _infiniteScrollController,
  fetchMore: () async {}, // fetching more data
  builder: (data) {} // building an item
)
// later
_infiniteScrollController.clearItems();
  

Как создать такой контроллер? Я использую flutter_hooks для локального управления состоянием, если это имеет значение.

Комментарии:

1. Я не понимаю, как это связано с моей проблемой. Я ищу способ создания контроллера, сам виджет — всего лишь пример.

Ответ №1:

Я не думаю, что ответ @ValdaXD описывает хорошее решение.

Обычно это решается также в собственных виджетах Flutter, таких как TextField или ScrollController — это класс контроллера, который расширяется ChangeNotifier .

Контроллер будет обрабатывать элементы и предоставлять общедоступный API для их очистки:

 class InfiniteScrollController extends ChangeNotifier {
  List<Widget> items = [];

  void clearItems() {
    items.clear();
    notifyListeners();
  }
}
  

Затем виджет может отображать элементы с помощью введенного контроллера:

 class InfiniteScroll extends StatefulWidget {
  InfiniteScroll({
    required this.controller
  });

  final InfiniteScrollController controller;

  @override
  State<StatefulWidget> createState() {
    return _InfiniteScrollState();
  }
}

class _InfiniteScrollState extends State<InfiniteScroll> {
  @override
  Widget build(BuildContext context) {
    return ListView(
      children: widget.controller.items,
    );
  }
}
  

Я создал сообщение в блоге с другим примером, но на ту же тему: контроллер для пользовательского виджета: https://www.flutterclutter.dev/flutter/tutorials/create-a-controller-for-a-custom-widget/2021/2149 /

Комментарии:

1. Этот ответ является лучшим решением. Принятый ответ выходит за рамки того, как собственные виджеты решают проблему. Сообщение в блоге от @Schnodderbalken — отличное руководство по решению этой проблемы.

2. Спасибо за ваш ответ, я пометил его как принятый.

3. ваша статья в блоге очень полезна.

4. Хороший ответ, но у меня есть одно сомнение по этому поводу. Я считаю, что это решение подразумевает сохранение состояния в контроллере. Что, если вы хотите, чтобы управляемый виджет содержал все состояния, а не разделял его внутри контроллера? Разве решение ValdaXDs не было бы лучше?

Ответ №2:

Я просто передаю функции, которые я хочу предоставить контроллеру.

 typedef MyTypedef(int value);

class MyController {
  VoidCallback myFunction;
  VoidCallback mySecondFunction;
  MyTypedef functionThatReturns;

  void dispose() {
    //Remove any data that's will cause a memory leak/render errors in here
    myFunction = null;
    mySecondFunction = null;
    functionThatReturns = null;
  }
}

class MyWidget extends StatefulWidget {
  const MyWidget({this.controller});
  final MyController controller;
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  @override
  void initState() {
    super.initState();
    MyController _controller = widget.controller;
    if (_controller != null) {
      _controller.myFunction = firstFunction;
      _controller.mySecondFunction = secondFunction;
      _controller.functionThatReturns = functionWithInt;
    }
  }

  void firstFunction() {
    print('Calling first function');
  }

  void secondFunction() {
    print('Calling second function');
  }

  void functionWithInt(int value) {
    print('Calling third function with value $value');
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
  

Тогда использование будет простым

 //We create a variable somewhere
  ...
  MyController controller;
  ...

  //We initialize it
  ...
  controller = MyController();
  ...

  //We assign it
  @override
  Widget build(BuildContext context) {
    return MyWidget(controller: controller);
  }
}

//When we cant to call a function
...
controller.myFunction();
...

//When we want to dispose it
...
controller.dispose();
...
  

Необходимо проделать небольшую работу, чтобы избежать исключений null, например, мы могли бы проверить, являются ли ссылки на контроллеры null перед вызовом функций, и выдать ошибку, но это решать вам.

Ответ №3:

Вы могли бы просто использовать GlobalKey. Я действительно не знаю, как создавать контроллеры, но это было бы моим решением. Это действительно похоже на GlobalKey, но я надеюсь, что это сработает для вас. Но я ничего не нашел о том, как делать контроллеры тааак … да.

 class InfiniteScroll extends StatefulWidget {
  InfiniteScrollController controller;
  InfiniteScroll({@required this.controller}) {}

  InfiniteScrollState createState() => InfiniteScrollState(controller);
}

class InfiniteScrollState extends State<InfiniteScroll> {
  InfiniteScrollController controller;

  InfiniteScrollState(this.controller) {
    this.controller.setParent(this);
  }

  Widget build(BuildContext context) {}
}


class InfiniteScrollController {
  InfiniteScrollState infiniteScroll;

  void setParent(InfiniteScrollState infiniteScroll) {
    this.infiniteScroll = infiniteScroll;
  }

  void clearItems() {
    infiniteScroll.setState(() {
      //clear Items
    });
  }

}
  

Комментарии:

1. Спасибо за ваш ответ, это похоже на хороший обходной путь, хотя принятый ответ более чистый