Home > Software engineering >  Flutter Callback in Pageview causing Exception
Flutter Callback in Pageview causing Exception

Time:04-29

I'm trying to create a pageview that I can load a widget into that is defined in another file. This works fine, except when I try to add a callback, I get the following error:

FlutterError (setState() or markNeedsBuild() called during build.

This error is triggered when the email entered is considered to be valid (that is, when the code in the email_entry.dart calls the callback function that was passed from the account_onboarding.dart file.) I haven't been able to determine why this is happening, and no tutorials on this subject seem to exist. I am still pretty new to Dart/Flutter, so I'm hoping someone can point out what's happening (and a fix) here.

Here is my code:
-Parent widget, account_onboarding.dart

import 'package:flutter/material.dart';
import 'package:page_view_indicators/page_view_indicators.dart';
import 'package:animated_title_screen/screens/email_entry.dart';

class AccountOnboarding extends StatefulWidget {
  const AccountOnboarding({Key? key}) : super(key: key);

  @override
  State<AccountOnboarding> createState() => _AccountOnboardingState();
}

class _AccountOnboardingState extends State<AccountOnboarding> {
  final _pController = PageController(initialPage: 0);
  final _currentPageNotifier = ValueNotifier<int>(0);
  final List<Widget> _pages = [];

  bool validEmail = false;                            //Callback should set this variable

  @override
  void initState() {
    super.initState();
    _pages.add(                                   //Add the EmailEntry widget to the list
      EmailEntry(emailIsValid: (p0) {
        setState(() {
          validEmail = p0;
        });
      },),
    );
    _pages.add(
      Container(
        color: Colors.blue,
        child: Text("Pg2"),
      ),
    );
    _pages.add(
      Container(
        color: Colors.green,
        child: Text("Pg3"),
      ),
    );
  }

  @override
  void dispose() {
    _pController.dispose();
    _currentPageNotifier.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          "Create Account",
          style: Theme.of(context).textTheme.headline5,
        ),
        centerTitle: true,
        actions: [
          IconButton(
            icon: const Icon(Icons.close),
            onPressed: () => Navigator.pop(context),
          ),
        ],
      ),
      body: Stack(
        fit: StackFit.expand,
        children: [
          Column(
            children: [
                Row(
                  children: [ 
                      Text(
                        "Step ${_currentPageNotifier.value   1} of ${_pages.length}",
                      ),            
                      CirclePageIndicator(
                        dotColor: const Color(0xFF323232),
                        selectedDotColor: const Color(0xFFE4231F),
                        size: 10,
                        selectedSize: 10,
                        currentPageNotifier: _currentPageNotifier,
                        itemCount: _pages.length,
                      ),                   
                  ],
                ),
                PageView(
                  controller: _pController,
                  onPageChanged: (index) {
                    setState(() {
                      _currentPageNotifier.value = index;
                    });
                  },
                  children: [
                    for (Widget p in _pages) p,           //Display all pages in _pages[]
                  ],
                ),          
              ElevatedButton(
                child: const Text("Continue"),
                onPressed: () => print("Pressed 2"),
                style: ElevatedButton.styleFrom(
                  primary: validEmail ? const Color(0xFFE1251B) : Colors.black,
                  textStyle: TextStyle(
                    fontWeight: FontWeight.bold,
                    fontSize: 15,
                  ),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

Here is the email_entry.dart code:

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

class EmailEntry extends StatefulWidget {
  final Function(bool) emailIsValid;                   //Function required in constructor
  const EmailEntry({Key? key, required this.emailIsValid}) : super(key: key); 

  @override
  State<EmailEntry> createState() => _EmailEntryState();
}

class _EmailEntryState extends State<EmailEntry> {
  final _emailController = TextEditingController();
  FocusNode _emailFocus = FocusNode();

  @override
  void initState() {
    super.initState();
    _emailController.addListener(() => setState(() {}));
    _emailFocus.addListener(() {
      print("Focus email");
    });
  }

  @override
  void dispose() {
    _emailController.dispose();
    super.dispose();
  }

  bool validateEmail(String email) {
    bool valid = RegExp(
            r"^[a-zA-Z0-9.!#$%&'* /=?^_`{|}~-] @[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$")
        .hasMatch(email);
    if (valid) {
      widget.emailIsValid(true);                             //Call the callback function
    }
    return valid;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
            Column(
              children: [
                  Text(
                    "YOUR EMAIL",
                    style: Theme.of(context).textTheme.headline2,
                  ),
                  Text(
                      "Please use an email address that you would like to make your login.",
                      style: Theme.of(context).textTheme.bodyText2,
                      textAlign: TextAlign.center,
                    ),
                Container(                                    
                  child: Text(
                    "Email Address",                    
                  ),
                ),
                TextField(
                  controller: _emailController,
                  keyboardType: TextInputType.emailAddress,
                  focusNode: _emailFocus,
                  suffixIcon: getTextFieldSuffix(emailController, _emailFocus),  //OFFENDING CODE
                ),
              ],
            ),
        ],
      ),
    );
  }
//THIS FUNCTION CAUSED THE ISSUE.  It is code I got from a youtube //tutorial.  Probably should have guessed.
  Widget getTextFieldSuffix(TextEditingController controller, FocusNode node) {
    if (controller.text.isNotEmpty && node.hasFocus) {
      return IconButton(
          color: Colors.grey.withAlpha(150),
          onPressed: () => controller.clear(),
          icon: const Icon(Icons.close));
    } else if (controller.text.isNotEmpty && !node.hasFocus) {
      return const Icon(
        Icons.check,
        color: Colors.green,
      );
    }
    return Container(
      width: 0,
    );
  }
}

CodePudding user response:

in initState,you need to call addPostFrameCallback. like this...

  @override
  void initState() {
    super.initState();
    ///Add This Line
    WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
      ///All of your code
    });
  }


CodePudding user response:

I found out that there is some code in my production version that calls a redraw every time the user enters a letter into the textfield for the email address. This was causing a problem because the screen was already being redrawn, and I was calling setState to redraw it again. I will edit the code shown above to include the offending code.

  • Related