Flutter: метод запуска на сборке виджета завершен

Я хотел бы иметь возможность запускать функции после завершения сборки / загрузки виджета, но я не уверен, как. Мой текущий вариант использования - проверить, прошел ли пользователь аутентификацию, а если нет, перенаправить в представление входа в систему. Я не хочу проверять раньше и выдвигать либо вид входа в систему, либо основной вид, это должно произойти после загрузки основного вида. Есть ли что-нибудь, что я могу использовать для этого?

Спасибо

16 ответов

Вы могли бы использовать

https://github.com/slightfoot/flutter_after_layout

который выполняет функцию только один раз после завершения макета. Или просто посмотрите на его реализацию и добавьте его в свой код:-)

Что в основном

  void initState() {
    super.initState();
    WidgetsBinding.instance
        .addPostFrameCallback((_) => yourFunction(context));
  }

ОБНОВИТЬ:

Упомянутый код уже не работает:

Не работает:

WidgetsBinding.instance
        .addPostFrameCallback((_) => yourFunction(context));

Рабочий код:

import 'package:flutter/scheduler.dart';

SchedulerBinding.instance.addPostFrameCallback((_) => yourFunction(context));

Лучшие способы сделать это,

1. WidgetsBinding

WidgetsBinding.instance.addPostFrameCallback((_) {
      print("WidgetsBinding");
    });

2. WidgetsBinding

SchedulerBinding.instance.addPostFrameCallback((_) {
  print("SchedulerBinding");
});

Это можно назвать внутри initState, оба будут вызываться только один раз после завершения рендеринга виджетов Build.

@override
  void initState() {
    // TODO: implement initState
    super.initState();
    print("initState");
    WidgetsBinding.instance.addPostFrameCallback((_) {
      print("WidgetsBinding");
    });
    SchedulerBinding.instance.addPostFrameCallback((_) {
      print("SchedulerBinding");
    });
  }

оба приведенных выше кода будут работать одинаково, поскольку оба используют одинаковую структуру привязки. Чтобы узнать разницу, найдите ссылку ниже.

https://medium.com/flutterworld/flutter-schedulerbinding-vs-widgetsbinding-149c71cb607f

Есть 3 возможных способа:

1) WidgetsBinding.instance.addPostFrameCallback((_) => yourFunc(context));

2) Future.delayed(Duration.zero, () => yourFunc(context));

3) Timer.run(() => yourFunc(context));

Что касается context, Мне это было нужно для использования в Scaffold.of(context) после того, как все мои виджеты были отрисованы.

Но, по моему скромному мнению, лучший способ сделать это:

void main() async {
  WidgetsFlutterBinding.ensureInitialized(); //all widgets are rendered here
  await yourFunc();
  runApp( MyApp() );
}

В flutter версии 1.14.6, Dart версии 28.

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

@override
void initState() {
super.initState();
print('hello girl');

WidgetsBinding.instance
    .addPostFrameCallback((_) => afterLayoutWidgetBuild());

}

Флаттер 1.2 - дротик 2.2

В соответствии с официальными рекомендациями и источниками, если вы хотите быть уверены, что также был нарисован последний кадр вашего макета, вы можете написать, например:

import 'package:flutter/scheduler.dart';

void initState() {
   super.initState();
   if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
        SchedulerBinding.instance.addPostFrameCallback((_) => yourFunction(context));
   }
}

Если вы ищете ReactNative's componentDidMount эквивалентно, у Флаттера есть это. Это не так просто, но работает точно так же. Во флаттере WidgetОни не обрабатывают свои события напрямую. Вместо этого они используют их State объект, чтобы сделать это.

class MyWidget extends StatefulWidget{

  @override
  State<StatefulWidget> createState() => MyState(this);

  Widget build(BuildContext context){...} //build layout here

  void onLoad(BuildContext context){...} //callback when layout build done
}

class MyState extends State<MyWidget>{

  MyWidget widget;

  MyState(this.widget);

  @override
  Widget build(BuildContext context) => widget.build(context);

  @override
  void initState() => widget.onLoad(context);
}

State.initState сразу будет вызван, когда экран закончит рендеринг макета. И никогда больше не будет вызываться даже при горячей перезагрузке, если вы находитесь в режиме отладки, пока явно не достигнет этого времени.

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

        @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
       Future.delayed(Duration(seconds: 3), () => yourFunction());
    });
  }

если у вас возникли проблемы с новым SDK и старым ответом, вы можете попробовать мое решение. Я тестировал его на версии 3.0.4 .

      WidgetsBinding.instance.endOfFrame.then(
  (_) {
    if (mounted) {
          // do some suff 
          // you can get width height of specific widget based on GlobalKey
       };
  },
);

Попробуйте SchedulerBinding,

 SchedulerBinding.instance
                .addPostFrameCallback((_) => setState(() {
              isDataFetched = true;
            }));

Если вы не хотите использовать WidgetsBindingили SchedulerBinding:

  • ИспользоватьFutureилиTimer(очень просто)

            Future<void> _runsAfterBuild() async {
      // This code runs after build ...
    }
    
    @override
    Widget build(BuildContext context) {
      Future(_runsAfterBuild); // <-- Use Future or Timer
      return Container();
    }
    
  • Ждите фиктивного будущего

            Future<void> _runsAfterBuild() async {
      await Future((){}); // <-- Dummy await
    
      // This code runs after build ...
    }
    
    @override
    Widget build(BuildContext context) {
      _runsAfterBuild();
      return Container();
    }
    

Для использования GetXSchedulerBindingвместоWidgetsBindingсделал работу

      SchedulerBinding.instance.addPostFrameCallback((_) {
  // your code here
});

мой английский плохой прости меня

      import 'package:flutter/material.dart';

class TestBox extends StatefulWidget {
  final Color color;
  final Duration delay;

  const TestBox({
    Key? key,
    this.color = Colors.red,
    this.delay = const Duration(seconds: 5),
  }) : super(key: key);

  @override
  _TestBoxState createState() => _TestBoxState();
}

class _TestBoxState extends State<TestBox> {
  String? label;

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

  void initialMembers() async {
    label = await fetchLabel();

    if (mounted) setState(() {});

    /// don't worry
    /// if `(!mounted)`, means wen `build` calld
    /// the label already has the newest value
  }

  Future<String> fetchLabel() async {
    await Future.delayed(widget.delay);
    print('fetchLabel call');
    return 'from fetchLabel()';
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      margin: EdgeInsets.symmetric(vertical: 12),
      duration: Duration(milliseconds: 500),
      width: 220,
      height: 120,
      color: label == null ? Colors.white : widget.color,
      child: Center(
        child: Text(label ?? 'fetching...'),
      ),
    );
  }
}

      Column(
  children: [
    TestBox(
      delay: Duration(seconds: 1),
      color: Colors.green,
    ),
    TestBox(
      delay: Duration(seconds: 3),
      color: Colors.yellow,
    ),
    TestBox(
      delay: Duration(seconds: 5),
      color: Colors.red,
    ),
  ],
),

другое решение, которое у меня очень хорошо сработало, - обернуть функцию, которую вы хотите вызвать, Future.delayed() как показано ниже:

        @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
       Future.delayed(Duration(seconds: 3), () => yourFunction());
    });
  }

У меня есть виджет Stateful, в котором я использую виджет плагина html_editor_enhanced. Это единственный способ установить в нем начальное сообщение.

      class _SendChatMessageState extends State<SendChatMessage> {
  final _htmlController = HtmlEditorController();

      @override
      void initState() {
        super.initState();
    
          Future.delayed(const Duration(seconds: 3), () {
            _htmlController.setText(widget.chatMessage.message ?? '');
          });
      }

Я попробовал addPostFrameCallback, но это не сработало, потому что JavaScript генерирует исключение «Редактор HTML все еще загружается, подождите, прежде чем оценивать этот JS...»

Если вы хотите сделать это только один раз, сделайте это, потому что фреймворк вызовет initState() метод ровно один раз для каждого создаваемого им объекта State.

 @override
  void initState() {
    super.initState();
    WidgetsBinding.instance
        .addPostFrameCallback((_) => executeAfterBuildComplete(context));
  }

Если вы хотите делать это снова и снова, как на спине, или перейти к следующему экрану и т. Д., То делайте это, потому что didChangeDependencies() Вызывается при изменении зависимости этого объекта State.

Например, если предыдущий вызов build сослался на InheritedWidget который позже изменился, платформа вызовет этот метод, чтобы уведомить этот объект об изменении.

Этот метод также вызывается сразу после initState. Звонить безопасноBuildContext.dependOnInheritedWidgetOfExactType из этого метода.

 @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    WidgetsBinding.instance
        .addPostFrameCallback((_) => executeAfterBuildComplete(context));
  }

Это ваша функция обратного вызова

executeAfterBuildComplete([BuildContext context]){
    print("Build Process Complete");
  }
Другие вопросы по тегам