Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI doesn't react to state changes after using Navigator.push #4156

Closed
Bruchess opened this issue Apr 25, 2024 · 4 comments
Closed

UI doesn't react to state changes after using Navigator.push #4156

Bruchess opened this issue Apr 25, 2024 · 4 comments
Assignees
Labels
question Further information is requested

Comments

@Bruchess
Copy link

Hello, I'm new in Flutter and I'm still learning state management with BLoC. I have a problem when I use Navigator.push. It's like the "context" of the BLoC is getting lost.

Let's say that I have declared these states:

@immutable 
abstract class MyStates {
  const MyStates();
}

@immutable class OtherState extends MyStates{
  const OtherState() ;
}

@immutable class WelcomePage extends MyStates{
  const WelcomePage() ;
}

And I have declared these events:

abstract class MyEvents {
  const MyEvents();
}

class UserThatHasToLogInDetected extends MyEvents {
  const UserThatHasToLogInDetected();
}

What I want is the users to be able to see what the app is about without logging in. However, if they try to use a special function of the app, then they will be requested to log in.

With this in mind, I wrote this BLoC:

class MyBloc extends Bloc<MyEvents, MyStates> {
  MyBloc() : super (const OtherState()) {
    
    on<UserThatHasToLogInDetected>((event, emit) {
      emit(const WelcomePage());
    });
}

Then in the main.dart file I have something like this:

void main()  {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider<MyBloc>(
      create: (context) => MyBloc(),
      child: MaterialApp(
        initialRoute: RouteGenerator.home,    //**Points to home.dart**
        onGenerateRoute: RouteGenerator.generateRoute... etc.
}

In the home.dart I have something like this:

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<BHappyBloc, BHappyState>(
      builder: (context, state) {
        if (state is WelcomePage) {
          return Welcome();  **//This makes the app to show the users the Welcome screen where they're required to log in**
        }  else {
          return const FirstPage();
        }
      },
    );
  }
}

At the beginning, the state is "OtherState" so, it will return FirstPage which have this:

class FirstPage extends StatefulWidget {
  const FirstPage({super.key});

  @override
  State<FirstPage> createState() => _FirstPageState();
}

class _FirstPageState extends State<FirstPage> {

  @override
  Widget build(BuildContext context) {
    
    return Scaffold(
    body: Center(
            child: ElevatedButton(
                onPressed: () {
                  context.read<MyBloc>().add(const UserThatHasToLogInDetected());
                },
                child: const Text("Do something special in the app")));
   }
}

So, when I press this button in FirstPage the state changes to WelcomePage and the app returns Welcome() which is the screen where the user is required to log in.

Until here everything works fine. However, let's say that in the first page I add another button that when pressed, it executes:
Navigator.of(context).pushNamed(RouteGenerator.secondPage);

In that "secondPage" I have exactly the same code I have in FirstPage:

class SecondPage extends StatefulWidget {
  const SecondPage({super.key});

  @override
  State<SecondPage> createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {

  @override
  Widget build(BuildContext context) {
    
    return Scaffold(
    body: Center(
            child: ElevatedButton(
                onPressed: () {
                  context.read<MyBloc>().add(const UserThatHasToLogInDetected());
                },
                child: const Text("Do something special in the app")));
   }
}

And finally we arrive to my problem. When I press the button in the second page, through some prints I can see that the state is effectively changing to "WelcomePage", but the app doesn't return "Welcome" it stays showing SecondPage.

  1. What's happening?

I have tested that if I add a Navigator.pop() after context.read<MyBloc>().add(const UserThatHasToLogInDetected()); in the SecondPage then it returns to the FirstPage and then it responds to the state change returning "Welcome".

  1. How can I manage my navigation in a way that it doesn't lose the BLoC?

Of course in my real code I have much more states, events and lines of code. But I wanted to explain my problem briefly. Thanks in advance.

@Bruchess Bruchess added the bug Something isn't working label Apr 25, 2024
@Melyca
Copy link

Melyca commented Apr 25, 2024

I have the same problem and I couldn't find the solution. 🤔🤔🤔

@ubert15
Copy link

ubert15 commented Apr 25, 2024

I've had the same problem for about 2 weeks and I still haven't resolved it...

@tenhobi tenhobi added question Further information is requested waiting for response Waiting for follow up and removed bug Something isn't working labels Apr 25, 2024
@tenhobi
Copy link
Collaborator

tenhobi commented Apr 25, 2024

This is not an issue with Bloc but with your design.

You have your bloc above MaterialApp, which is great -- all pages can access it.

Imagine routes/pages as "papers" and widgets as "drawings" on that paper. Based on what you described, your pages are Home Page and Second Page. On page Home there might be "drawings" welcome or first. That means when you are on Home Page, you can actually see bloc builder to build something.

But when you navigate to Second Page, again imagine a paper, you cannot see changes on Home Page, but only your current page, no matter if Home Page actually still exists or not. So if you want to create navigation based on states, you need to choose different approach, since your does not do that.

I would recommend i.e. creating states based on auth state like AuthInProgress, LoggedIn and UserNeedsToLogin etc. and then you have two options.

Use BlocListener under provider (you may need Builder above BlocListener to provide current context) and listen to changes there and there you can redirect to different pages (display different "papers"), or you can use something like GoRouter package which I recommend anyway and GoRouter object has redirect callback which you can implement like this maybe, you get the point...

MyStates? lastState;

FutureOr<String?> handleRedirect(
    BuildContext context,
    GoRouterState state,
  ) {
    final appState = context.read<MyBloc>().state;

    // Same route.
    final isSameState = appState == lastState;
    if (isSameState) {
      return null;
    }
    lastState = appState;

    // Auth in progress.
    if (appState is AuthInProgress) return null;

    if (appState is UserNeedsToLogin) {
      return '/login';
    }
    
    return null;

I hope you understand the concepts why your solution couldnt do that thing you wanted. Feel free to ask more questions for sure. :)

@Bruchess
Copy link
Author

Thank you very much for your early answer. So, I think that in conclusion is not a good idea to think about screens as if they were states. I'm currently redesigning my app.

@felangel felangel removed the waiting for response Waiting for follow up label Apr 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants