Text Field within Stream Builder causes widget reload
Asked Answered
A

1

0

I'm having trouble with my TextField widgets. Every time the user taps on them, the whole widget reloads, and the keyboard disappears. I believe I tracked the problem down to a streambuilder I have on main checking for user authentication. Here's a summarized version of my MyApp widget in my main.dart:

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  // this widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    SizeConfig.init(context);
    final auth = FirebaseAuth.instance;
    return MultiProvider(
      providers: [
        //Providers go in here...
      ],
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'livit',
        theme: ThemeData.dark().copyWith(
          scaffoldBackgroundColor: mobileBackgroundColor,
        ),
        home: StreamBuilder(
          stream: auth.authStateChanges(),
          builder: (context, snapshot) {
            //over here we have logic to go to the login screen or the main page
          },
        ),
      ),
    );
  }
}

Once the app checks if the user's authenticated, it either takes them to the login screen or the main page. And that works like a charm. Unfortunately, the user tries to tap on any text field, they can't type, because as soon as they focus, the widget reloads.

So I made a clean flutter project, and cooked up a widget to test if everything went fine:

class TextInputTest extends StatefulWidget {
  const TextInputTest({super.key, required this.title});

  final String title;

  @override
  State<TextInputTest> createState() => _TextInputTestState();
}

class _TextInputTestState extends State<TextInputTest> {
  final TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: TextField(
          controller: _controller,
        ),
      ),
    );
  }
}

And it worked, which is neat! I want to note that all of my text-field-using screens are formatted similarly, as in they are stateful and the controller is declared inside the state.

Now, if I plop my perfectly working test widget where the stream builder usually is, the widget works just fine. However, if I replace either the login screen or the main page, the test widget has the same exact problem, where the whole widget will reload on focus and the user will never be able to pull up the screen.

Can you please help me out to solve this problem? I sort of need my users to be able to type stuff.

Thanks in advance for any help provided, and please let me know if you need more information!

PS: I did check this thread out, and none of the answers seemed to be fruitful.

Aloisia answered 30/1 at 2:20 Comment(5)
Do you put const before login screen and main page? For example: const LoginScreen()Thierry
DO NOT DO THIS: stream: auth.authStateChanges(),. Do not make the stream in the stream: parameter of a StreamBuilder. See youtu.be/sqE-J8YJnpg for details.Melvamelvena
Dhafin, I do have a const keyword before those pages. And Randal, thanks so much, I'll check out your video in the morning and get back to you!Aloisia
You should try instantiating the LoginScreen or HomeScreen classes outside the StreamBuilder in a variable and then try using the variable in place of their instances ! This trick saved me a lot of time !!Wholesale
Does this answer your question? Flutter FutureBuilder keeps Firing and rebuilding. It Generates a New Random Number on Every Rebuild - How can I Prevent This?Obeisance
A
0

So it turns out by putting calling auth.authStateChanges() inside the build method (in this case inside the named stream parameter), the stream is called and re-created every single time that build is called. Whenever the keyboard is pulled up, build is called, therefore setting everything back to how it was, including my textfield in its initial, unfocused state.

The solution in this case is to make the widget stateful if it's not already, declare a variable to hold the stream in the state (not in any method), and then give it the value auth.authStateChanges(). In my case, I had to make the variable late and use the override initState() to set the value, since auth couldn't be reached in an initializer.

Big thanks to Randal Schwartz for spotting the issue and posting his video explanation to the problem.

Aloisia answered 30/1 at 18:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.