Rebuilding app with StreamBuilder (beginner) | Flutter

Viewed 59

This is my widget tree:

Widget tree

I have StreamBuilder (PURPLE) which is re-building app after clicking FloatingActionButton (RED).

Where the problem is

StreamBuilder rebuilds only objects under him, that means UserSettingsPage (GREEN) isn't getting rebuild. I want him to be rebuild.

My thoughts about fixing it

1.) I think I can maybe move my StreamBuilder above MateriallApp, but I don't know how to implement it.

2.) Maybe i can somehow move UserSettingsPage next to HomePage

I am open for every solution.

(main.dart)

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MultiProvider(providers: [
    ChangeNotifierProvider(create: (_) => GoogleSignInProvider()),
    ChangeNotifierProvider(create: (_) => ThemesProvider()),
    ChangeNotifierProvider(create: (_) => ZmienneClass())
  ], child: const MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => MaterialApp(
        debugShowCheckedModeBanner: false,
        theme: Provider.of<ThemesProvider>(context).darkModeOn
            ? ThemeOptions.white
            : ThemeOptions.black,
        home: Scaffold(
          body: StreamBuilder(
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return const Center(child: CircularProgressIndicator());
              } else if (snapshot.hasData) {
                //// 2 OPCJA - DODAĆ TU NAVIGATOR I PRZECZYTAĆ TO
                return const HomePage();
              } else if (snapshot.hasError) {
                return const Center(child: Text("Something went Wrong.."));
              } else {
                return const LoginPage();
              }
            },
            stream: FirebaseAuth.instance.authStateChanges(),
          ),
        ),
      );
}

I am opening UserSettingsPage via clicking on FloatingActionButton (RED) which is in UserPage.

Here is his code :

 onPressed: () {
            Navigator.push(context,
                MaterialPageRoute(builder: (context) => const UserSettingsPage()));
          },

(HomePage)

import 'package:flany/userpage.dart';
import 'package:flany/widgets/game.dart';
import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  const HomePage({Key? key, firTime, secTime}) : super(key: key);
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _selectedIndex = 1;

  @override
  Widget build(BuildContext context) {
    var screens = [
      Container(),
      Game(),
      const UserPage(),
    ];
    return Scaffold(
        bottomNavigationBar: BottomNavigationBar(
            onTap: (index) {
              setState(() {
                _selectedIndex = index;
              });
            },
            items: const <BottomNavigationBarItem>[
              BottomNavigationBarItem(icon: Icon(Icons.score), label: ""),
              BottomNavigationBarItem(
                  icon: Icon(Icons.games_rounded), label: ""),
              BottomNavigationBarItem(icon: Icon(Icons.person), label: ""),
            ],
            currentIndex: _selectedIndex),
        body: IndexedStack(index: _selectedIndex, children: screens));
  }
}

(UserPage)

import 'package:flany/settingspage.dart';
import 'package:flutter/material.dart';

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

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

class _UserPageState extends State<UserPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
              context,
              MaterialPageRoute(
                  builder: (context) => const UserSettingsPage()));
        },
        child: const Icon(
          Icons.settings,
        ),
      ),
      body: ListView(
        children: const [
          // SOME WIDGETS
        ],
      ),
    );
  }
}

(UserSettingsPage)

import 'package:flutter/material.dart';

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

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

class _UserSettingsPageState extends State<UserSettingsPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(children: const [
        // BUTTON WHO IS EXECUTING STREAMBUILDER (onpressed code of it)
        onPressed: () {
          final provider =
             Provider.of<GoogleSignInProvider>(context, listen: false);
          provider.logout();
        },
      ]),
    );
  }
}


1 Answers

What exactly does your UserSettingsPage need to have updated?

If it only needs to be updated before it is pushed, it seems this would be a simple solution:

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {  // Added {}
    AsyncSnapshot? mySnap;  // Added a variable here

    return MaterialApp( // Added return
      debugShowCheckedModeBanner: false,
      // theme: Provider.of<ThemesProvider>(context).darkModeOn
      //     ? ThemeOptions.white
      //     : ThemeOptions.black,
      home: Scaffold(
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            Navigator.push(
                context,
                MaterialPageRoute(
                  // Added an argument to UserSettingsPage, which you could use 
                  // for whatever it needs to be rebuilt with:
                    builder: (context) => const UserSettingsPage(mySnap)));
          },
        ),
        body: StreamBuilder(
          builder: (context, snapshot) {
            mySnap = snapshot;  // Added an update of the variable sent to UserSettingsPage()
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const Center(child: CircularProgressIndicator());
            } else if (snapshot.hasData) {
              //// 2 OPCJA - DODAĆ TU NAVIGATOR I PRZECZYTAĆ TO
              return const HomePage();
            } else if (snapshot.hasError) {
              return const Center(child: Text("Something went Wrong.."));
            } else {
              return const LoginPage();
            }
          },
          stream: FirebaseAuth.instance.authStateChanges(),
        ),
      ),
    );
  }
}

If instead you want it to be rebuilt while you're looking at it, because of changes in the StreamBuilder(), then yes, you need to put the UserSettingsPage() downstream of the StreamBuilder in the Widget tree. Or use a Provider or some other solution.


Edit:

Since you have now told me that you want the UserSettingsScreen to update while looking at it, in response to a button press on that same screen, which triggers a change in login status, here is one way to solve that using a Provider (as I can see you are already using one on this page):

// The way it is written right now, this could just as well be a
// StatelessWidget, but I'm keeping it stateful since that's what you wrote.
class UserSettingsPage extends StatefulWidget {
  const UserSettingsPage({Key? key}) : super(key: key);

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

class _UserSettingsPageState extends State<UserSettingsPage> {
  @override
  Widget build(BuildContext context) {
    // Declare your provider first thing in the build method, and make it listen:
    final provider = Provider.of<GoogleSignInProvider>(context, listen: true);
    return Scaffold(
      body: ListView(children: [
        // Below, I have used your provider to change the output on the screen.
        // Since I have not seen your provider file, I don't know how it works,
        // but I assume there is a way for it to tell if the user is logged in
        // or not! :) Plz change your code to the correct way. This is just
        // an example:
        Text('You are logged ${provider.loggedIn ? 'in' : 'out'}'),
        // BUTTON WHO IS EXECUTING STREAMBUILDER
        FloatingActionButton(
          onPressed: () {
          provider.logout();
        },
        )
      ]),
    );
  }
}

// My provider file used above would look something like this:
class GoogleSignInProvider extends ChangeNotifier {
  bool loggedIn = false;

  void login() {
    googleObject.login();
    loggedIn = true;
    notifyListeners();
  }

  void logout() {
    googleObject.logout();
    loggedIn = false;
    notifyListeners();
    // The above will notify your UserSettingsPage that 
    // loggedIn has changed to false. It will update its
    // UI accordingly.
  }
}

Do tell me how it goes!

Related