Невозможно установить состояние вне контейнера в поставщике - PullRequest
0 голосов
/ 03 мая 2020

Я понимаю, что проблема в жизненном цикле, в котором я пытаюсь установить состояние в Провайдере до визуализации Виджета, но где я могу это сделать. Только в виджете контейнера? Но я не могу этого сделать, если у меня нет кнопки или чего-то еще.

Я надеюсь, у вас возникла проблема здесь.

Буду признателен за любые подсказки!

моя ошибка:

setState() or markNeedsBuild() called during build.

или

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

, если я установлю здесь состояние

  _detectLastPage() {
    int currentPage = this.currentStep == null ? 1 : this.currentStep + 1;

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

Мой виджет:

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

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

  final double radius = 10.0;
  final double distance = 4.0;

  Container stepper;
  Container nextArrow;
  bool lastPage;
  WelcomeBloc _welcomeBloc;

  Footer({
    this.activeColor,
    this.inactiveColor,
    this.currentStep,
    this.totalSteps,
    this.duration,
    this.onFinal,
    this.onStart,
  }) {
    this._detectLastPage();
    this._makeStepper();
    this._makeNextArrow();
  }

  @override
  Widget build(BuildContext context) {
    print('footer is launching');

    final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
    _welcomeBloc = _welcome;
    // this._welcomeBloc.lastPage = true; // I'd like to set the state here

    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,
          RaisedButton(
            child: Text('kdfljds'),
            onPressed: () {
              print(_welcomeBloc.lastPage);
              _welcomeBloc.lastPage = true; // I can access from here BUT CANNOT access outside this container
            },
          )
        ],
      ),
    );
  }

  _makeCirle(activeColor, inactiveColor, position, currentStep) {
    currentStep = currentStep ?? 0;
    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 < totalSteps; i++) {
      circles.add(
        _makeCirle(this.activeColor, this.inactiveColor, i, this.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.duration ?? Duration(milliseconds: 500),
                curve: Curves.easeInOut,
              );
            },
            child: Icon(
              Icons.arrow_forward,
            )),
      ),
    );
  }

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

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

  _detectLastPage() {
    int currentPage = this.currentStep == null ? 1 : this.currentStep + 1;

    if (currentPage == 1 && this.currentStep == null) {
      this._onFirstPage();
    } else if (currentPage == this.totalSteps) {
      this.lastPage = true;
      this._onLastPage();
    } else {
      this.lastPage = false;
    }
  }
}


BlocFile

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

[![error screen with StateLess, since I use Provider][1]][1]

That's In a StateFul Widget

Там я звоню вот так:

_detectLastPage() {
    int currentPage =
        this.widget.currentStep == null ? 1 : this.widget.currentStep + 1;

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

StateLess И без SetState кажется, что та же ошибка ...

эта ошибка, если я вызываю изнутри initState из вашего примера. Просто забыл прикрепить его

enter image description here

Ответы [ 3 ]

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

Если я правильно понял, вы пытаетесь смоделировать PageView навигацию по некоторому кружку ниже (Индикаторы).

Для этого есть много хороших ресурсов, а также такие пакеты, как: This пример или этот пакет

Но для вашего кода я написал его в 2 подходах:

Первый подход

Это ваш код и поставщик услуг.


import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(Home());
}

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (BuildContext context) => WelcomeBloc(),
      child: Consumer<WelcomeBloc>(
        builder: (BuildContext context, value, Widget child) {
          PageController controller = value.controller;
          print('object');
          return MaterialApp(
            home: Scaffold(
              body: Stack(
                children: <Widget>[
                  PageView(
                    controller: controller,
                    children: List.generate(
                        10, (i) => Center(child: Text('Page $i'))),
                    onPageChanged: (i) {
                      value.currentPage = i;
                    },
                  ),
                  Footer(
                    activeColor: Colors.red,
                    duration: Duration(seconds: 1),
                    inactiveColor: Colors.yellow,
                    onFinal: () {},
                    onStart: () {},
                    totalSteps: 10,
                  )
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

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

  Footer({
    this.activeColor,
    this.inactiveColor,
    this.totalSteps,
    this.duration,
    this.onFinal,
    this.onStart,
    this.radius = 10.0,
    this.distance = 4.0,
  });

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

class _FooterState extends State<Footer> {
  bool lastPage;
  WelcomeBloc _welcomeBloc;

  @override
  Widget build(BuildContext context) {
    final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);

    _welcomeBloc = _welcome;

    // this._welcomeBloc.lastPage = true; // I'd like to set the state here

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

  _makeCircle(activeColor, inactiveColor, position, currentStep) {
    currentStep = currentStep ?? 0;
    Color color = (position == currentStep) ? activeColor : inactiveColor;

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

  _makeNextArrow() {
    return Container(
      child: Padding(
        padding: const EdgeInsets.only(right: 8.0),
        child: GestureDetector(
            onTap: () async {
              await _welcomeBloc.nextPage(widget.duration, Curves.easeInOut);
              setState(() {});
            },
            child: Icon(
              Icons.arrow_forward,
            )),
      ),
    );
  }

  _makeStepper() {
    return Container(
      child: Row(
        children: List.generate(
          widget.totalSteps,
          (i) => _makeCircle(
            this.widget.activeColor,
            this.widget.inactiveColor,
            i,
            _welcomeBloc.currentPage,
          ),
        ),
      ),
    );
  }

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

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

  _detectLastPage() {
    int currentPage =
        _welcomeBloc.currentPage == null ? 1 : _welcomeBloc.currentPage + 1;

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

class WelcomeBloc extends ChangeNotifier {
  final PageController _controller = PageController();
  int _currentPage = 0;
  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();
  }

  PageController get controller => _controller;

  Future<void> nextPage(Duration duration, Curve curve) {
    currentPage = controller.page.floor() + 1;
    return controller.nextPage(duration: duration, curve: curve);
  }
}

Второй подход

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

import 'package:flutter/material.dart';

void main() {
  runApp(Home());
}

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  PageController controller = PageController(initialPage: 0);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Stack(
          children: <Widget>[
            PageView(
              controller: controller,
              children: List.generate(
                10,
                (i) => Center(child: Text('Page $i')),
              ),
              onPageChanged: (page) {
                setState(() {});
              },
            ),
            Footer(
              currentPage: controller.hasClients ? controller.page.floor() : 0,
              activeColor: Colors.red,
              inactiveColor: Colors.yellow,
              totalSteps: 10,
              onTap: () async {
                await controller.nextPage(
                  duration: Duration(seconds: 1) ?? Duration(milliseconds: 500),
                  curve: Curves.easeInOut,
                );
                setState(() {});
              },
            )
          ],
        ),
      ),
    );
  }
}

class Footer extends StatelessWidget {
  final int totalSteps;
  final Color activeColor;
  final Color inactiveColor;
  final double radius;
  final double distance;
  final int currentPage;
  final GestureTapCallback onTap;

  Footer({
    this.activeColor,
    this.inactiveColor,
    this.totalSteps,
    this.radius = 10.0,
    this.distance = 4.0,
    this.currentPage,
    this.onTap,
  });

  @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>[
          _makeStepper(),
          _makeNextArrow(),
        ],
      ),
    );
  }

  _makeCircle(activeColor, inactiveColor, position) {
    Color color = (position == currentPage) ? activeColor : inactiveColor;

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

  _makeNextArrow() {
    return Container(
      child: Padding(
        padding: const EdgeInsets.only(right: 8.0),
        child: GestureDetector(
            onTap: onTap,
            child: Icon(
              Icons.arrow_forward,
            )),
      ),
    );
  }

  _makeStepper() {
    return Container(
      child: Row(
        children: List.generate(
          totalSteps,
          (i) => _makeCircle(
            this.activeColor,
            this.inactiveColor,
            i,
          ),
        ),
      ),
    );
  }
}
1 голос
/ 03 мая 2020

Вы не можете использовать метод setState в StatelessWidget. Преобразуйте его в StatefulWidget и вызовите setState в методе initState.

Как это

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 initState(){
    this._detectLastPage();
    this._makeStepper();
    this._makeNextArrow();
    final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
    _welcomeBloc = _welcome;
    setState((){
     this._welcomeBloc.lastPage = true; // Where to use setState
    });
  }

  @override
  Widget build(BuildContext context) {
    print('footer is launching');


    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,
          RaisedButton(
            child: Text('kdfljds'),
            onPressed: () {
              print(_welcomeBloc.lastPage);
              _welcomeBloc.lastPage = true; // I can access from here BUT CANNOT access outside this container
            },
          )
        ],
      ),
    );
  }

  _makeCirle(activeColor, inactiveColor, position, currentStep) {
    currentStep = currentStep ?? 0;
    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 < totalSteps; i++) {
      circles.add(
        _makeCirle(this.activeColor, this.inactiveColor, i, this.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.duration ?? Duration(milliseconds: 500),
                curve: Curves.easeInOut,
              );
            },
            child: Icon(
              Icons.arrow_forward,
            )),
      ),
    );
  }

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

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

  _detectLastPage() {
    int currentPage = this.currentStep == null ? 1 : this.currentStep + 1;

    if (currentPage == 1 && this.currentStep == null) {
      this._onFirstPage();
    } else if (currentPage == this.totalSteps) {
      this.lastPage = true;
      this._onLastPage();
    } else {
      this.lastPage = false;
    }
  }
}

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

Итак, решение моей ошибки в didChangeDependencies хуке.

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

Итак, мне просто нужно было запустить его до или после виджета. построено.

Вот как это выглядит в коде:

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

PS: но я столкнулся с другой проблемой, связанной с точностью данных. Внутри этого крючка я мог получить свойство класса только на один шаг позади точного.

...