Хук didChangeDependencies в виджете Flutter включает не точные данные класса - PullRequest
0 голосов
/ 04 мая 2020

В моем коде ниже я борюсь с LifeCyrles во Flutter, где я могу обновить свое состояние в Provider, ВНУТРИ, только в didChangeDependencies hook или в виджете шаблона (через события, повешенные на кнопках или около того).

Хорошо, я не против того, что у меня работает только ловушка didChangeDependencies, НО, когда моя логика c в ранее упомянутой ловушке зависит от некоторых свойств класса. У меня возникают проблемы с точностью данных класса. Я получаю данные на один шаг позади (так как они называются до build hook, я думаю).

Я не могу запустить эту логику c в хуке сборки, потому что он включает в себя запрос на изменение состояния в Provider. Если я пытаюсь изменить там состояние, я получаю либо эту ошибку:

setState() or markNeedsBuild() called during build.

, либо эту

The setter 'lastPage=' was called on null. Receiver: null Tried calling: lastPage=true

Что я хочу сделать : У меня есть виджет-обертка, который содержит три других виджета: нижний колонтитул, заголовок и pageViewer. Когда я достигаю последней страницы, мне нужно уведомить об этом мой виджет-оболочку, чтобы он реагировал соответствующим образом и скрывал верхний и нижний колонтитулы.

Буду признателен за любую помощь здесь!

Сфокусированный код:

Вот проблема и должна быть решена

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:ui_flutter/screens/welcome/welcome_bloc.dart';
import 'package:flutter/scheduler.dart';

class _FooterState extends State<Footer> {

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

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
    _welcomeBloc = _welcome;
    // this._detectLastPage();
  }

  @override
  Widget build(BuildContext context) {

    return Container(
      alignment: Alignment.bottomCenter,
      padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 30.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          this.stepper,
          this.nextArrow,
        ],
      ),
    );
  }

  _detectLastPage() {
    // Here I've got inaccurate data

    print(this.widget.currentStep);
}
}

Я уже пробовал некоторые другие хуки, такие как планировщик, но, возможно, Я сделал что-то не так там.

SchedulerBinding.instance
        .addPostFrameCallback((_) => this._detectLastPage());

Он вызывается только один раз в первом раунде сборки и все. Мне не хватает Angular крючка здесь AfterViewInit. Здесь было бы удобно.

или Mounted в VueJS


Это остальная часть моего кода, если вы хотите увидеть всю картину. Если у вас есть какие-либо предложения по архитектуре, структуре или что-то еще, пожалуйста. Это высоко ценится, так как я новичок во Флаттере.

main.dart

import 'package:flutter/material.dart';
import 'package:ui_flutter/routing.dart';
import 'package:provider/provider.dart';
import 'screens/welcome/welcome_bloc.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => WelcomeBloc()),
      ],
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        initialRoute: '/welcome',
        onGenerateRoute: RouteGenerator.generateRoute,
      ),
    );
  }
}

welcome.dart (моя оболочка)

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
import 'package:ui_flutter/screens/welcome/welcome_bloc.dart';
import './footer.dart';
import './viewWrapper.dart';
import './header.dart';
// import 'package:ui_flutter/routing.dart';

class Welcome extends StatefulWidget {
  @override
  _WelcomeState createState() => _WelcomeState();
}

class _WelcomeState extends State<Welcome> {
  WelcomeBloc _welcomeBloc;

  @override
  Widget build(BuildContext context) {
    final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
    this._welcomeBloc = _welcome;
    print('Welcome: _welcome.currentPage - ${this._welcomeBloc.lastPage}');

    return Scaffold(
      body: SafeArea(
        child: Stack(
          children: <Widget>[
            ViewerWrapper(),
            Footer(
              currentStep: _welcomeBloc.currentPage,
              totalSteps: 3,
              activeColor: Colors.grey[800],
              inactiveColor: Colors.grey[100],
            ),
            WelcomeHeader,
          ],
        ),
      ),
    );
  }
}

welcomeBlo c .dart (мое состояние через провайдера)

import 'package:flutter/material.dart';

class WelcomeBloc extends ChangeNotifier {
  PageController _controller = PageController();
  int _currentPage;
  bool _lastPage = false;

  bool get lastPage => _lastPage;

  set lastPage(bool value) {
    _lastPage = value;
    notifyListeners();
  }

  int get currentPage => _currentPage;

  set currentPage(int value) {
    _currentPage = value;
    notifyListeners();
  }

  get controller => _controller;

  nextPage(Duration duration, Curves curve) {
    controller.nextPage(duration: duration, curve: curve);
  }
}

footer.dart (вот где у меня проблемы с данными в самом низу кода - метод _detectLastPage)

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:ui_flutter/screens/welcome/welcome_bloc.dart';
import 'package:flutter/scheduler.dart';

class Footer extends StatefulWidget {
  final int currentStep;
  final int totalSteps;
  final Color activeColor;
  final Color inactiveColor;
  final Duration duration;
  final Function onFinal;
  final Function onStart;

  Footer({
    this.activeColor,
    this.inactiveColor,
    this.currentStep,
    this.totalSteps,
    this.duration,
    this.onFinal,
    this.onStart,
  }) {}

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

class _FooterState extends State<Footer> {
  final double radius = 10.0;
  final double distance = 4.0;
  Container stepper;
  Container nextArrow;
  bool lastPage;
  WelcomeBloc _welcomeBloc;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
    _welcomeBloc = _welcome;
    this._detectLastPage();
  }

  @override
  Widget build(BuildContext context) {
    this._makeStepper();
    this._makeNextArrow();

    return Container(
      alignment: Alignment.bottomCenter,
      padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 30.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          this.stepper,
          this.nextArrow,
        ],
      ),
    );
  }

  _makeCirle(activeColor, inactiveColor, position, currentStep) {
    currentStep = currentStep == null ? 0 : currentStep - 1;
    Color color = (position == currentStep) ? activeColor : inactiveColor;

    return Container(
      height: this.radius,
      width: this.radius,
      margin: EdgeInsets.only(left: this.distance, right: this.distance),
      decoration: BoxDecoration(
          color: color,
          border: Border.all(color: activeColor, width: 2.0),
          borderRadius: BorderRadius.circular(50.0)),
    );
  }

  _makeStepper() {
    List<Container> circles = List();

    for (var i = 0; i < widget.totalSteps; i++) {
      circles.add(
        _makeCirle(this.widget.activeColor, this.widget.inactiveColor, i,
            this.widget.currentStep),
      );
    }

    this.stepper = Container(
      child: Row(
        children: circles,
      ),
    );
  }

  _makeNextArrow() {
    this.nextArrow = Container(
      child: Padding(
        padding: const EdgeInsets.only(right: 8.0),
        child: GestureDetector(
            onTap: () {
              _welcomeBloc.controller.nextPage(
                duration: this.widget.duration ?? Duration(milliseconds: 500),
                curve: Curves.easeInOut,
              );
            },
            child: Icon(
              Icons.arrow_forward,
            )),
      ),
    );
  }

  _onLastPage() {
    if (this.widget.onFinal != null) {
      this.widget.onFinal();
    }
  }

  _onFirstPage() {
    if (this.widget.onStart != null) {
      this.widget.onStart();
    }
  }

  _detectLastPage() {
    // Here I've got inaccurate data 

    int currentPage =
        this.widget.currentStep == null ? 1 : this.widget.currentStep;

    if (currentPage == 1 && this.widget.currentStep == null) {
      this._onFirstPage();
    } else if (currentPage == this.widget.totalSteps) {
      print('lastPage detected');
      setState(() {
        this.lastPage = true;
      });
      _welcomeBloc.lastPage = true;
      this._onLastPage();
    } else {
      setState(() {
        this.lastPage = false;
      });
      _welcomeBloc.lastPage = false;
    }
  }
}

Заранее спасибо!

Ответы [ 2 ]

1 голос
/ 07 мая 2020

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

Вот как я это делаю:

Создание Provider, который содержит данные для вас во время выполнения. (Это может быть Bloc в вашем случае). Придерживайтесь одной архитектуры, не пытайтесь ставить провайдеров и блоки в одном проекте. Оба используются для управления состоянием, и только использование одного было бы хорошей практикой.

Во-вторых, Register провайдеры, использующие ChangeNotificationProvider или любые другие виджеты, которые выполняют аналогичную работу по восстановлению дочернего виджета, когда данные меняется.

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

Для вашего случая. Если вы хотите скрыть верхний и нижний колонтитулы, как только дойдете до последней страницы, вы можете объявить переменную, скажем, isLastPage, установленную в false по умолчанию у вашего провайдера. Затем оберните виджет, т.е. header и footer, с помощью ChangeNotificationListner

Теперь позвольте этому виджету решить, что он должен делать, основываясь на значении isLastPage, либо скрыть себя, либо показать.

Надеюсь, это поможет!

0 голосов
/ 08 мая 2020

В долгосрочной перспективе я, похоже, обнаружил Mounted ловушку жизненного цикла во Flutter, которая реализована с помощью Future.microtask. В отличие от .addPostFrameCallback:

SchedulerBinding.instance
        .addPostFrameCallback((_) => this._detectLastPage());

, который срабатывает только один раз, как InitState (но только в конце выполнения сборки), Future.microtask может быть помещен в блок build и вызываться после каждое изменение и обновление состояния.

Это не решает проблему с неточным состоянием в didChangeDependencies ловушке, но предоставляет другой способ выполнения выполнения после сборки.

Кредиты для текущего решения @ Abion47

пример

Future.microtask(() => this._detectLastPage());
...