Flutter отримує контекст у методі initState


85

Я не впевнений, чи initStateє для цього правильною функцією. Що я намагаюся досягти, це перевірити, коли сторінка відображається, щоб виконати деякі перевірки і на їх основі відкрити a, AlertDialogщоб зробити деякі налаштування, якщо це необхідно.

У мене є Сторінка, яка має стан. Це initStateфункція виглядає так:

@override
void initState() {
    super.initState();
    if (!_checkConfiguration()) {
        _showConfiguration(context);
    }
}

Ось _showConfigurationтак:

void _showConfiguration(BuildContext context) {
    AlertDialog dialog = new AlertDialog(
        content: new Column(
            children: <Widget>[
                new Text('@todo')
            ],
        ),
        actions: <Widget>[
            new FlatButton(onPressed: (){
                Navigator.pop(context);
            }, child: new Text('OK')),
        ],
    );

    showDialog(context: context, child: dialog);
}

Якщо є кращий спосіб зробити цю перевірку, і при необхідності викликати модальний, будь ласка, вкажіть мені у правильному напрямку, я шукав функцію onStateабо onRenderфункцію, або зворотний виклик, який я міг би призначити buildфункції, яка буде викликана при рендері, але змогли його знайти.


Редагувати: Тут, здається, схожа проблема: Flutter Redirect на сторінку в initState

Відповіді:


113

Контекст змінної члена можна отримати під час, initStateале не можна використовувати для всього. Це з метушні для initStateдокументації:

Ви не можете використовувати [BuildContext.inheritFromWidgetOfExactType]цей метод. Однак, [didChangeDependencies]буде викликаний відразу після цього методу, і [BuildContext.inheritFromWidgetOfExactType] може бути використаний там.

Ви можете перенести свою логіку ініціалізації didChangeDependencies, однак, можливо, це не саме те, що ви хочете, оскільки це didChangeDependenciesможе бути викликано кілька разів у життєвому циклі віджета.

Якщо ви замість цього зробите асинхронний виклик, який делегує ваш дзвінок до того часу, поки віджет не буде ініціалізований, ви можете використовувати контекст, як ви задумали.

Простий спосіб зробити це - використовувати майбутнє.

Future.delayed(Duration.zero,() {
  ... showDialog(context, ....)
}

Інший спосіб, який може бути більш "правильним", полягає у використанні планувальника флаттера для додавання зворотного виклику після кадру:

SchedulerBinding.instance.addPostFrameCallback((_) {
  ... showDialog(context, ....)
});

І нарешті, ось маленький трюк, який я люблю робити, щоб використовувати асинхронні виклики у функції initState:

() async {
  await Future.delayed(Duration.zero);
  ... showDialog(context, ...)      
}();

Ось повністю розроблений приклад із використанням простого Future.delayed:

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

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

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  bool _checkConfiguration() => true;

  void initState() {
    super.initState();
    if (_checkConfiguration()) {
      Future.delayed(Duration.zero,() {
        showDialog(context: context, builder: (context) => AlertDialog(
          content: Column(
            children: <Widget>[
              Text('@todo')
            ],
          ),
          actions: <Widget>[
            FlatButton(onPressed: (){
              Navigator.pop(context);
            }, child: Text('OK')),
          ],
        ));
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
    );
  }
}

Маючи більше контексту з ОП, що міститься у коментарях, я можу дати трохи краще рішення їх конкретної проблеми. Залежно від програми, ви можете прийняти рішення, виходячи з того, яку сторінку показувати, залежно від того, чи відкривається програма вперше, тобто встановлено homeщось інше. І діалоги не обов’язково є найкращим елементом інтерфейсу на мобільному; може бути краще показати повну сторінку з налаштуваннями, які їм потрібно додати, і наступну кнопку.


Сторінка в питанні є першою сторінкою , і це називається через MaterialApp«s homeвласність. Тому я насправді не роблю там поштовху. Не могли б ви дати мені приклад, як це зробити у buildфункції? В даний час він просто повертає новий Scaffoldз appBar, drawer, bodyіfloatingActionButton
Wawa

Одним із варіантів, який мені спадає на думку, було б передати усі віджети, що використовуються в модалі, у функції, і на домашніх сторінках Scaffoldвідображати їх (як жоден модальний), якщо перевірка не вдається, а в іншому випадку просто відображати домашню сторінку. Це, однак, мені здається хакі.
wawa

4
це дуже погано. перше місце, де ви можете отримати доступ до контексту, - це didChangeDependenciesметод
Razvan Cristian Lung

1
@wawa - Я трохи виправив приклад. Я насправді забув, що contextнасправді є змінною-членом стану = D. Отже, вам не потрібне логічне значення, ви можете просто безпосередньо використовувати Future.delayedin у вашому initstate. Однак це все-таки потрібно - без цього ви отримаєте помилки твердження про те, що намагаєтесь натиснути під час натискання.
rmtmckenzie

2
у моєму випадку там написано "Невизначений контекст імені" в initState
temirbek,

41

Обгортання Future

  @override
  void initState() {
    super.initState();
    _store = Store();
    new Future.delayed(Duration.zero,() {
      _store.fetchContent(context);
    });
  }

4

Більшість прикладів initState()у цьому потоці можуть бути роботами для "UI" речей, таких як "Діалогове вікно", що має місце у кореневому питанні цього потоку.

Але, на жаль, це не працює для мене, коли я застосовую його, щоб отримати contextдля " Постачальника ".

Отже, я вибираю didChangeDependencies()підхід. Як згадувалося у прийнятій відповіді, він має застереження, тобто його можна викликати кілька разів у життєвому циклі віджета. Однак впоратися з цим досить просто. Просто використовуйте одну допоміжну змінну, яка boolзапобігає множинним викликам всередині didChangeDependencies(). Ось приклад використання _BookListStateкласу зі змінною _isInitializedяк головної "пробки" "кількох викликів":

class _BookListState extends State<BookList> {
  List<BookListModel> _bookList;
  String _apiHost;
  bool _isInitialized; //This is the key
  bool _isFetching;

  @override
  void didChangeDependencies() {
    final settingData = Provider.of<SettingProvider>(context);
    this._apiHost = settingData.setting.apiHost;
    final bookListData = Provider.of<BookListProvider>(context);
    this._bookList = bookListData.list;
    this._isFetching = bookListData.isFetching;

    if (this._isInitialized == null || !this._isInitialized) {// Only execute once
      bookListData.fetchList(context);
      this._isInitialized = true; // Set this to true to prevent next execution using "if()" at this root block
    }

    super.didChangeDependencies();
  }

...
}

Ось журнали помилок, коли я намагаюся зробити initState()підхід:

E/flutter ( 3556): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: 'package:provider/src/provider.dart': Failed assertion: line 242 pos 7: 'context.owner.debugBuilding ||
E/flutter ( 3556):           listen == false ||
E/flutter ( 3556):           debugIsInInheritedProviderUpdate': Tried to listen to a value exposed with provider, from outside of the widget tree.
E/flutter ( 3556):
E/flutter ( 3556): This is likely caused by an event handler (like a button's onPressed) that called
E/flutter ( 3556): Provider.of without passing `listen: false`.
E/flutter ( 3556):
E/flutter ( 3556): To fix, write:
E/flutter ( 3556): Provider.of<SettingProvider>(context, listen: false);
E/flutter ( 3556):
E/flutter ( 3556): It is unsupported because may pointlessly rebuild the widget associated to the
E/flutter ( 3556): event handler, when the widget tree doesn't care about the value.
E/flutter ( 3556):
E/flutter ( 3556): The context used was: BookList(dependencies: [_InheritedProviderScope<BookListProvider>], state: _BookListState#1008f)
E/flutter ( 3556):
E/flutter ( 3556): #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:46:39)
E/flutter ( 3556): #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5)
E/flutter ( 3556): #2      Provider.of
package:provider/src/provider.dart:242
E/flutter ( 3556): #3      _BookListState.initState.<anonymous closure>
package:perpus/…/home/book-list.dart:24
E/flutter ( 3556): #4      new Future.delayed.<anonymous closure> (dart:async/future.dart:326:39)
E/flutter ( 3556): #5      _rootRun (dart:async/zone.dart:1182:47)
E/flutter ( 3556): #6      _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 3556): #7      _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter ( 3556): #8      _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
E/flutter ( 3556): #9      _rootRun (dart:async/zone.dart:1190:13)
E/flutter ( 3556): #10     _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 3556): #11     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1021:23)
E/flutter ( 3556): #12     Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
E/flutter ( 3556): #13     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:397:19)
E/flutter ( 3556): #14     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:428:5)
E/flutter ( 3556): #15     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
E/flutter ( 3556):

1
У вас є така помилка, оскільки ви не використовуєте параметр "Listen: false". Постачальник виявляє, що не викликається з дерева віджетів (усередині методу "побудови").
Лукас Руеда,

1
Дякую, що вказав на це @LucasRueda, схоже, я зробив "послухай: помилково" або context.read(), але робив "гаряче переїзд" замість "перезапуску" на моєму VSCode. Отримавши ваше повідомлення, я дійсно намагаюся "перезапустити" після того, як застосував "listen: false" до свого постачальника. Я підтверджую, що це справді було спричинено "послухай: правда" або context.watch(). Незабаром оновить мою відповідь.
Bayu

1

Ми можемо використовувати глобальний ключ як:

class _ContactUsScreenState extends State<ContactUsScreen> {

    //Declare Global Key
      final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

    //key
    Widget build(BuildContext context) {
        return  Scaffold(
            key: _scaffoldKey,
            appBar: AppBar(
              title: Text('Contact Us'),
            ),
            body:
       }

    //use
      Future<void> send() async {
        final Email email = Email(
          body: _bodyController.text,
          subject: _subjectController.text,
          recipients: [_recipientController.text],
          attachmentPaths: attachments,
          isHTML: isHTML,
        );

        String platformResponse;

        try {
          await FlutterEmailSender.send(email);
          platformResponse = 'success';
        } catch (error) {
          platformResponse = error.toString();
        }

        if (!mounted) return;

        _scaffoldKey.currentState.showSnackBar(SnackBar(
          content: Text(platformResponse),
        ));
      }


}

0

Просте використання Timer.run()

@override
void initState() {
  super.initState();
  Timer.run(() {
    // you have a valid context here
  });
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.