0

Currently facing a problem with conditional logic and PageView widget. Let's say that the PageView will dynamically generate 3 pages. In those 3 pages different widgets will be generated. One of the widgets is a button (called "Next"), which is a PageController, but that widget has to be replaced by a button widget that is supposed to submit (called "Submit") the whole form (the PageView is wrapped in a form).

It seems obvious, just write conditional logic that compares the current page of the PageView to the length of the PageView (PageView is populated with a List, so it is easy to get the length). Then switch the widgets when the right conditions meet: when current page equals to 3, change the widget. Unfortunately, the PageView renders the "Next" button on every page. So only when I get to the last page and then click "Next" again will it change to "Submit". It is supposed to be "Submit", when the user gets on the last page.

const int TRIVIA_STARTING_TIME = 10;

class TriviaOneForm extends StatefulWidget {
  final UserRepository _userRepository;

  TriviaOneForm({Key key, @required UserRepository userRepository})
      : assert(userRepository != null),
        _userRepository = userRepository,
        super(key: key);

  State<TriviaOneForm> createState() => _TriviaOneFormState();
}

class _TriviaOneFormState extends State<TriviaOneForm> {
  final TextEditingController _answerController = TextEditingController();

  UserRepository get _userRepository => widget._userRepository;

  TriviaOneBloc _triviaOneBloc;
  PageController _pageController;

  Timer _timer;

  bool _isLoadingScreen;
  bool _isNextOrSubmitButton;
  int _start;
  int _indexOfCarouselItem;
  List<int> _selectedValList;

  List _triviaDataList;

  @override
  void initState() {
    super.initState();
    _isLoadingScreen = true;
    _getTriviaData();
    _pageController = PageController();
    _indexOfCarouselItem = 0;
    _isNextOrSubmitButton = true;
    _selectedValList = [0, 0, 0, 0, 0];
    _triviaDataList = [];
    _start = TRIVIA_STARTING_TIME;
    _triviaOneBloc = BlocProvider.of<TriviaOneBloc>(context);
    _answerController.addListener(_onAnswerChanged);
  }

  @override
  void dispose() {
    if (_timer != null) {
      _timer.cancel();
    }
    _pageController.dispose();
    super.dispose();
  }

  void startTimer() {
    const oneSec = const Duration(seconds: 1);
    _timer = new Timer.periodic(
      oneSec,
      (Timer timer) => setState(
        () {
          if (_start < 1) {
            timer.cancel();
          } else {
            _start = _start - 1;
          }
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return BlocListener<TriviaOneBloc, TriviaOneState>(
      listener: (context, state) {
        if (state.isFailure) {
          Scaffold.of(context)
            ..hideCurrentSnackBar()
            ..showSnackBar(
              SnackBar(
                content: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    Text('Submition Failure'),
                    Icon(Icons.error)
                  ],
                ),
                backgroundColor: Colors.red,
              ),
            );
        }
        if (state.isSubmitting) {
          Scaffold.of(context)
            ..hideCurrentSnackBar()
            ..showSnackBar(
              SnackBar(
                content: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    Text('Submitting Answers...'),
                  ],
                ),
              ),
            );
        }
        if (state.isSuccess) {
          BlocProvider.of<TriviaOneBloc>(context).add(Submitted());
        }
      },
      child: BlocBuilder<TriviaOneBloc, TriviaOneState>(
        builder: (context, state) {
          return _isLoadingScreen
              ? _displayLoadScreen()
              : Padding(
                  padding: EdgeInsets.all(20.0),
                  child: Form(
                    child: PageView(
                      physics: NeverScrollableScrollPhysics(),
                      controller: _pageController,
                      reverse: false,
                      scrollDirection: Axis.horizontal,
                      children: _triviaDataList.map<Widget>((triviaData) {
                        return ListView(
                          shrinkWrap: true,
                          children: <Widget>[
                            Text(triviaData.getQuestion),
                            ListView(
                              shrinkWrap: true,
                              children: triviaData.getAnswers
                                  .map<Widget>((triviaAnswer) {
                                int index =
                                    triviaData.getAnswers.indexOf(triviaAnswer);
                                return ListTile(
                                  title: Text(triviaAnswer.getAnswer),
                                  leading: Radio(
                                    value: index,
                                    groupValue:
                                        _selectedValList[_indexOfCarouselItem],
                                    onChanged: (int value) {
                                      setState(() {
                                        print(value);
                                        _selectedValList[_indexOfCarouselItem] =
                                            value;
                                      });
                                    },
                                  ),
                                );
                              }).toList(),
                            ),
                            _isNextOrSubmitButton ? _nextButton() : _submitButton(),
                            RaisedButton(
                              onPressed: () {
                                startTimer();
                              },
                              child: Text('Start'),
                            ),
                            Text('$_start'),
                          ],
                        );
                      }).toList(),
                    ),
                  ),
                );
        },
      ),
    );
  }

  Widget _triviaControlButton(PageController pageController) {
    if (0 < _triviaDataList.length) {
      return RaisedButton(
        child: Text('Next'),
        onPressed: () {
          pageController.nextPage(
              duration: Duration(seconds: 1), curve: Curves.easeInOut);
          print('Next');
        },
      );
    } else if (pageController.page.toInt() == _triviaDataList.length) {
      return RaisedButton(
        child: Text('Submit'),
        onPressed: () {
          print('Submit');
        },
      );
    } else {
      return RaisedButton(
        child: Text('Error'),
        onPressed: () {
          print('Error');
        },
      );
    }
  }

  Widget _displayLoadScreen() {
    return Container(
      alignment: Alignment(0.0, 0.0),
      child: CircularProgressIndicator(),
    );
  }

  void _onAnswerChanged() {
    _triviaOneBloc.add(AnswerChanged(answer: _answerController.text));
  }

  void _getTriviaData() async {
    var data = _userRepository.retrieveTriviaData();
    // Await trivia data to be retrieved from firebase
    await data.getDocuments().then((collection) {
      collection.documents.forEach((document) {
        TriviaData triviaData = TriviaData();
        List<TriviaAnswer> triviaAnswerList = List<TriviaAnswer>();
        // Iterate through all of the answers for a question
        // Create a list of TriviaAnswer objects to hold key and value
        document.data['answers'].forEach((key, value) {
          TriviaAnswer triviaAnswer = TriviaAnswer();
          triviaAnswer.setAnswer = key;
          triviaAnswer.setAnswerValue = value;
          triviaAnswerList.add(triviaAnswer);
        });
        // Assign question String and answer List to TriviaData
        // Add all data to data list
        triviaData.setAnswers = triviaAnswerList;
        triviaData.setQuestion = document.data['question'];
        _triviaDataList.add(triviaData);
      });
    });
    setState(() {
      _isLoadingScreen = false;
    });
  }

  Widget _nextButton() {
    return RaisedButton(
      child: Text('Next'),
      onPressed: () {
        if (_indexOfCarouselItem < _triviaDataList.length) {
          _pageController.nextPage(
              duration: const Duration(milliseconds: 100),
              curve: Curves.easeInOut);
          setState(() {
            _start = TRIVIA_STARTING_TIME;
            _indexOfCarouselItem += 1;
          });
        }
        if (_indexOfCarouselItem == _triviaDataList.length) {
          Future.delayed(const Duration(seconds: 0), () {
            setState(() {
              _isNextOrSubmitButton = false;
            });
          });
        }
        try {
          if (_timer != null || !_timer.isActive) {
            startTimer();
          }
        } catch (_) {
          print('Error: Timer is already disabled');
        }
      },
    );
  }

  Widget _submitButton() {
    return RaisedButton(
      child: Text('Submit'),
      onPressed: () {
        print(_selectedValList);
        _userRepository.storeTriviaToFirebase();
        setState(() {
          if (_timer != null || _timer.isActive) {
            _timer.cancel();
          }
        });
      },
    );
  }
}

EDIT 1: This is the updated code I use for the button to populate in the PageView. I am setting a String to initial value "Next" then updating it when _indexOfCarouselItem + 2 == _triviaDataList.length is true. The updated value will be "Submit", when the condition is met.

Widget _triviaControlButton() {
    return RaisedButton(
      child: Text(buttonText),
      onPressed: () {
        _pageController.nextPage(
            duration: const Duration(milliseconds: 100),
            curve: Curves.easeInOut);
        if (_indexOfCarouselItem + 2 == _triviaDataList.length) {
          setState(() {
            buttonText = "Submit";
          });
        }
        if (_indexOfCarouselItem < _triviaDataList.length) {
          setState(() {
            _start = TRIVIA_STARTING_TIME;
            _indexOfCarouselItem += 1;
          });
        }
        print(_indexOfCarouselItem);
        print(_triviaDataList.length);
      },
    );
  }

1 Answers1

1

I’m on phone now so I can’t guarantee the code I’ll post is ok, but you’ll get the idea.

First: I don’t think you need 2 buttons if they are equals on size etc. so you can implement something like this:

child: Text( _indexOfCarouselItem += 1 !=  _triviaDataList.length
 ? 'Next' : 'Submit')

And then use the same logic in the onPressed:

onPressed() {
   _indexOfCarouselItem += 1 != _triviaDataList.length ? doSomethibg : doSomethingDifferent;
}

Edit: Ok if I understand correctly the problem right now is that because of the transition the button says "Submit" but there are no question yet? If this is the case you can like you said, add delay, but I think a better approach will be wire the text of the button with the question. I mean you can keep the actual logic (because it works) and add something like this:

child: Text( _indexOfCarouselItem += 1 != _triviaDataList.length && questionText != "" ? 'Next' : 'Submit')

This logic can be applied in if ... else ... block too.

Edit 2: try this one:

Widget _triviaControlButton() {
    return RaisedButton(
      child: Text(buttonText),
      onPressed: () {
        _pageController.nextPage(
            duration: const Duration(milliseconds: 100),
            curve: Curves.easeInOut);
        if (_indexOfCarouselItem < _triviaDataList.length) {
          setState(() {
            _start = TRIVIA_STARTING_TIME;
            _indexOfCarouselItem += 1;
          });
if (_indexOfCarouselItem == _triviaDataList.length) {
          setState(() {
            buttonText = "Submit";
          });
        }
      },
    );
  }
i6x86
  • 1,557
  • 6
  • 23
  • 42
  • I really want to use _pageController.page instead of _indexOfCarouselItem. I have to subtract 1 from _triviaDataList.length, because _indexOfCarouselItem starts at zero and we are taking _triviaDataList length. So when the user is on the second page and presses the "Next", the condition is met, but right before the transition to the last page the button text is changed to "Submit". So the user sees the "Submit" button on the second page. So I either have to make the transition to 0 seconds or use delay for the text. – Elonas Marcauskas Mar 31 '20 at 02:10
  • Ok I think you must add 1 to _indexOf... and I can see why you just don’t do it programmatically. I’ll edit the answer. Despite that I think there’s no problem to apply the same logic with pageController you just have to compare the numbers. – i6x86 Mar 31 '20 at 02:20
  • I already did that, the addition I mean. It produces the current result. – Elonas Marcauskas Mar 31 '20 at 02:21
  • I think I have to see the entire chunk of code, but it’s near impossible on 4.7” screen right now, so if nobody answer I’ll take a look tomorrow. – i6x86 Mar 31 '20 at 02:31
  • I am pretty sure you made a typo in the answer edit. There should not be a "+=" after _indexOfCarouselItem/, but "+" only. – Elonas Marcauskas Apr 01 '20 at 01:32
  • Well it’s my swift background :) sorry! – i6x86 Apr 01 '20 at 01:38
  • I think you understood it a little incorrectly. Right now the text of the button on the 2nd page (which is right before the last one) displays "Next". But once the user taps "Next" again the text immediately changes to "Submit" before turning to the last page. SO the user sees the changed text of the button before he or she is supposed to. – Elonas Marcauskas Apr 01 '20 at 01:50
  • I tried your solution, but probably implemented it incorrectly. I instantiated an empty string questionText in the class. But now "Submit" is displayed on every button. – Elonas Marcauskas Apr 01 '20 at 01:53
  • Ok now I see what’s your problem. Btw by questionText I mean the variable that you use to store the question or text you are displaying because I supposed it initial state were null bit now I see it might not be the case don’t mind it.’Ill edit the answer because I saw a better approach and I hope it will work this time. – i6x86 Apr 01 '20 at 02:19
  • Ok before I edit the answer I need you to try this: create var buttonText = ‘next, then use it like a text for the button. Then in the page controller, where you have the delay put between the curl brackets setState and use the same logic, if it’s the last page set the state of the buttonText to “submit’. I’m on the phone again so I don’t want to edit the answer before see if it works. – i6x86 Apr 01 '20 at 02:30
  • I edited the original question. I updated the code used for the button. Take a look at it. I hope I understood what you were talking about in your last comment. With the current code I am still experiencing one of the issues - seeing "Submit" on 2nd page before transitioning to the 3rd page. – Elonas Marcauskas Apr 02 '20 at 02:43
  • Sorry I was bussy, so why this line here if (_indexOfCarouselItem + 2 == _triviaDataList.length)? Why the "+ 2" and not + 1 instead? – i6x86 Apr 03 '20 at 16:58
  • Because if I set it at +1, then _indexOfCarouselItem = 3, when on the last page, and the button text is still "Next". When the user is on the last page and taps "Next" only then the button changes to "Submit". If I set is to +2, the button changes to "Submit" on the last page, but the button starts to change to "Submit" on the 2nd page. A small glimpse of "Submit" is seen when transitioning to the 3rd page. – Elonas Marcauskas Apr 04 '20 at 00:49
  • First I think you could set your initial _indexOfCarouselitem to 1, this way you can get rid of the indexofcarrousel + 1 logic. Second: You don’t need 2 if statements you can put the entire logic in only one. I’ll try to edit the answer if can’t do it now I’ll take look tomorrow. – i6x86 Apr 04 '20 at 01:34
  • Just edited the answer, but as you can guess, I’m on the phone right now, so it might need some fixes. – i6x86 Apr 04 '20 at 01:43
  • I initialized _indexOfCarousel item to 1, so _indexOfCarousel = 1. I also adapted your "EDIT 2". It gives me the same result as previously. It does not seem the Flutter allows to set the state of a page right before rendering the page onto the screen. I am creating each page by using a List object. Wondering, if setting up the List with the respective buttons, before PageView is setup, would work. – Elonas Marcauskas Apr 04 '20 at 15:37