felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.79k stars 3.39k forks source link

Doc request: How to use bloc across pages? #74

Closed realtebo closed 5 years ago

realtebo commented 5 years ago

Let's extended counter example, where all is working well.

Imagine I add a thir fab action that uses navigator.pushReplacement to navigate a different page.

The destination page needs to see counter value.

How to?

felangel commented 5 years ago

@realtebo you can achieve that by using BlocProvider to access the CounterBloc using the BuildContext.

class ThirdPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CounterBloc _counterBloc = BlocProvider.of<CounterBloc>(context);
    return BlocBuilder(
      bloc: _counterBloc,
      builder: (BuildContext context, int count) {
        return Text('$count');
      }
  }
}

Does that help?

realtebo commented 5 years ago

I was following this approach by myself, but I get

I/flutter ( 6351): The following assertion was thrown building OtherPage(dirty): I/flutter ( 6351): BlocProvider.of() called with a context that does not contain a Bloc of type CounterBloc. I/flutter ( 6351): No ancestor could be found starting from the context that was passed to I/flutter ( 6351): BlocProvider.of(). This can happen if the context you use comes from a widget above the I/flutter ( 6351): BlocProvider. I/flutter ( 6351): The context used was: I/flutter ( 6351): OtherPage(dirty)

This my target page code

import '../counter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class OtherPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    final CounterBloc _counterBloc = BlocProvider.of<CounterBloc>(context);

    return MaterialApp(
      title: 'Other Page',
      home: BlocProvider<CounterBloc>(
        bloc: _counterBloc,
        child: Center(
          child: Text(
            _counterBloc.currentState.toString(),
            style: TextStyle(fontSize: 24.0),
          ),
        ),
      ),
    );
  }

}
felangel commented 5 years ago

If you want your Bloc to be accessible globally (across different routes) you need to make sure that the BlocProvider is an ancestor of the MaterialApp widget.

class App extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _AppState();
}

class _AppState extends State<App> {
  final CounterBloc _counterBloc = CounterBloc();

  @override
  Widget build(BuildContext context) {
    return BlocProvider<CounterBloc>(
      bloc: _counterBloc,
      child: MaterialApp(
        title: 'Flutter Demo',
        home: CounterPage(),
      ),
    );
  }

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

Also, there's no need to have multiple BlocProviders for the same bloc. A BlocProvider makes the bloc available to all widgets in it's subtree.

realtebo commented 5 years ago

I'm doing this. [thanks for fast reply]

main.dart


void main() {
  BlocSupervisor().delegate = SimpleBlocDelegate();
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  final CounterBloc _counterBloc = CounterBloc();

  @override
  Widget build(BuildContext context) {
    return BlocProvider<CounterBloc>(
      bloc: _counterBloc,
      child: MaterialApp(
        title: 'Flutter Demo',
        home: CounterPage(),
      ),
    );
  }

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

counter page dart

import 'package:bloc_youtube_video/counter_bloc.dart';
import 'package:bloc_youtube_video/counter_event.dart';
import 'package:bloc_youtube_video/page/other_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CounterBloc _counterBloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: BlocBuilder<CounterEvent, int>(
        bloc: _counterBloc,
        builder: (BuildContext context, int count) {
          return Center(
            child: Text(
              '$count',
              style: TextStyle(fontSize: 24.0),
            ),
          );
        },
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                _counterBloc.dispatch(Increment());
              },
            ),
          ),
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.remove),
              onPressed: () {
                _counterBloc.dispatch(Decrement());
              },
            ),
          ),
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.pageview),
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (BuildContext context) {
                      return OtherPage();
                    },
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

The problematic one: other page dart

import '../counter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class OtherPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    final CounterBloc _counterBloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Other page')),
      body:  Center(
          child: Text(
            _counterBloc.currentState.toString(),
            style: TextStyle(fontSize: 24.0),
          ),
        ),
    );
  }

}

When I navigate to other page, screen became black and got exception

I/flutter ( 6694): ══╡ EXCEPTION CAUGHT BY SCHEDULER LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 6694): The following assertion was thrown during a scheduler callback:
I/flutter ( 6694): There are multiple heroes that share the same tag within a subtree.
I/flutter ( 6694): Within each subtree for which heroes are to be animated (typically a PageRoute subtree), each Hero
I/flutter ( 6694): must have a unique non-null tag.
I/flutter ( 6694): In this case, multiple heroes had the following tag: <default FloatingActionButton tag>
I/flutter ( 6694): Here is the subtree for one of the offending heroes:
I/flutter ( 6694): # Hero(tag: <default FloatingActionButton tag>, state: _HeroState#0d7ed)

And is not understandable

felangel commented 5 years ago

You need to provide a heroTag for each of the FloatingActionButton widgets. This has nothing to do with the Bloc library and is part of how Flutter works. You can't have more than one FloatingActionButton per page without uniquely identifying them using a heroTag.

Change counter_page.dart to:

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CounterBloc _counterBloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: BlocBuilder<CounterEvent, int>(
        bloc: _counterBloc,
        builder: (BuildContext context, int count) {
          return Center(
            child: Text(
              '$count',
              style: TextStyle(fontSize: 24.0),
            ),
          );
        },
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              heroTag: 0,
              child: Icon(Icons.add),
              onPressed: () {
                _counterBloc.dispatch(CounterEvent.increment);
              },
            ),
          ),
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              heroTag: 1,
              child: Icon(Icons.remove),
              onPressed: () {
                _counterBloc.dispatch(CounterEvent.decrement);
              },
            ),
          ),
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              heroTag: 2,
              child: Icon(Icons.pageview),
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute<OtherPage>(
                    builder: (BuildContext context) {
                      return OtherPage();
                    },
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
realtebo commented 5 years ago

Oh yes, now it works. A final question, I hope:

In the other page I need block builder only If I must dispatch events, right?

So I can simply have

class OtherPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CounterBloc _counterBloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Other page')),
      body: Center(
        child: Text(
          _counterBloc.currentState.toString(),
          style: TextStyle(fontSize: 24.0),
        ),
      ),
    );
  }
}

Right?

If this is almost all, your plugin is the most flexible at the minor code rows cost.

EDIT: I just discovered: https://felangel.github.io/bloc/#/coreconcepts

I Promise I read it very well. !

felangel commented 5 years ago

You only need BlocBuilder if you want to be able to render different widgets in response to different states.

Think of BlocBuilder like StreamBuilder; whenever the Bloc's state changes, the builder function will be called with the new state (in this case the integer) and you can render whatever you want.

You don't need BlocBuilder to dispatch events; in order to dispatch you just need to have access to the bloc itself and call dispatch (_counterBloc.dispatch(CounterEvent.increment)).

In this case you can just print currentState but I would recommend using BlocBuilder for most cases instead of directly accessing the bloc's state because it makes the UI code a bit cleaner (especially in more complex examples).

Haha yeah definitely check out the documentation 👍 I am still making lots of updates to the documentation so it should have even more details and examples over the next few weeks.

Did I answer all of your questions?

felangel commented 5 years ago

Closing this for now. Feel free to comment if you have any more questions!

realtebo commented 5 years ago

Sorry, I'm here again. I'm playing with your plugins !

I understand that I can have a BlockProvider as ancestor of MaterialApp, so I can share the bloc between pages without loosing states.

My question now is how to use a bloc provider wth 2 blocs, should I nest it?

realtebo commented 5 years ago

Could it be a good solution ? https://medium.com/@danaya/multiple-blocs-in-flutter-and-how-to-communicate-between-them-e441282dc62a

Combine multiple blocs? Like in react, combining reducers, where it allows me to splice state but with ease of handling.

felangel commented 5 years ago

@realtebo you can do either one. Nesting BlocProviders is perfectly fine and if you want for convenience you can combine Blocs. I would slightly advise against combining blocs because it is less flexible but it's totally up to you.

basketball-ico commented 5 years ago

@felangel Hello again :D.

I have some GlobalBlocs above MaterialApp, and I can access to this bloc in any route.

Then in a nested Route I created a BlocProvider, And I want to use in a Pages that this Screen Push. But I have the error BlocProvider.of() called with a context that does not contain a Bloc of type ....

I read this:

If you want your Bloc to be accessible globally (across different routes) you need to make sure that the BlocProvider is an ancestor of the MaterialApp widget.

So If my bloc need to be access, when push via Navigator, Is necessary to convert this Bloc a GlobalBloc?

I don't like this idea because is a NestedBloc so i don't like to see at the top haha, and also a have a lot of Blocs like this, and the MaterialApp will be `crazy" with a lot of a lot of blocs above...

How can I solve this?

Thanks

felangel commented 5 years ago

Hey 👋

You can just pass the Blocs via constructor in that case. Let me know if that helps!

basketball-ico commented 5 years ago

Yes, thanks. but I use pushNamed and I pass the bloc like argument, but i don't know if this pass a reference or a copy :(

Navigator.of(context).pushNamed(
                            'SomeRouteName',
                            arguments: _someBloc);

and then in the pushed page

_someBloc = ModalRoute.of(context).settings.arguments;
felangel commented 5 years ago

Primitives are passed by value and objects are passed by reference. 👍

yang-f commented 5 years ago

Hey 👋

You can just pass the Blocs via constructor in that case. Let me know if that helps!

I don't think it's a good solution, but i not have a better one. 😃

felangel commented 5 years ago

@yang-f I’ll update the docs later today with more information on that topic. You have 3 options though:

Imo it’s not a big deal to pass the bloc via constructor into the new page and then provide it again but like I said, I’ll add a new recipe for how to pass a bloc throughout the widget tree later today 👍

yang-f commented 5 years ago

@yang-f I’ll update the docs later today with more information on that topic. You have 3 options though:

  • wrap your MaterialApp with a BlocProvider for global access
  • use named routes and setup the BlocProvider to be scoped for the named route
  • pass the bloc via constructor into the new page and then use BlocProvider to make it available in the remaining subtree
  • use your another DI library to provide the blocs

Imo it’s not a big deal to pass the bloc via constructor into the new page and then provide it again but like I said, I’ll add a new recipe for how to pass a bloc throughout the widget tree later today 👍

Now we suppose have 3 pages: A,B,C(all they depend anyBloc) and A hold a anyBloc instance. page A -> page B(anyBloc) -> page C(anyBloc form B); then we nav back form C -> B(anyBloc from C)? the lifecycle‘s complexity of bloc will grow exponentially。

lingster commented 5 years ago

@felangel : thanks for creating this really useful library/framework, which I'm just getting my head around. One question: is it better to have your blocs defined at MaterialApp level then use BlocProvider.of<MyBloc>(context); or are there advantages or disadvantages to passing blocs to widgets via the constructor? Are there any performance considerations?

luandedeng commented 5 years ago

@yang-f I’ll update the docs later today with more information on that topic. You have 3 options though:

  • wrap your MaterialApp with a BlocProvider for global access
  • use named routes and setup the BlocProvider to be scoped for the named route
  • pass the bloc via constructor into the new page and then use BlocProvider to make it available in the remaining subtree
  • use your another DI library to provide the blocs

Imo it’s not a big deal to pass the bloc via constructor into the new page and then provide it again but like I said, I’ll add a new recipe for how to pass a bloc throughout the widget tree later today 👍

@felangel , I am using Bloc pattern and named routes in my project. I am also facing problem about access bloc across routes. I read the recipe about bloc accesshttps://felangel.github.io/bloc/#/recipesflutterblocaccess?id=route-access, but I still have no idea on how to use route access with named routes. Could you please give more explain on "use named routes and setup the BlocProvider to be scoped for the named route"? It will be great if you can also give a code example. Here is my code about named route: "pageA": (context) => BlocProvider.value( value: BlocProvider.of<BlocA>(context), child: new PageA(), ), when run it, it gives an error that "BlocProvider.of(context)" returns null.

felangel commented 5 years ago

@luandedeng I'll update the recipe sometime tomorrow with a section on named routes. Thanks for bringing that up 👍

FilipKvestak commented 4 years ago

Hello,

I'm stuck with something, still learning dart/flutter.

How would You use 2+ blocs, INSIDE of 1 bloc?

e.g in Your todos app, filtered_todos_bloc.dart:

class FilteredTodosBloc extends Bloc<FilteredTodosEvent, FilteredTodosState> {
  final TodosBloc _todosBloc;
  StreamSubscription _todosSubscription;

  FilteredTodosBloc({@required TodosBloc todosBloc})
      : assert(todosBloc != null),
        _todosBloc = todosBloc {
    _todosSubscription = todosBloc.listen((state) {
      if (state is TodosLoaded) {
        add(UpdateTodos((todosBloc.state as TodosLoaded).todos));
      }
    });
  }

  @override
  FilteredTodosState get initialState {
    final currentState = _todosBloc.state;
    return currentState is TodosLoaded
        ? FilteredTodosLoaded(currentState.todos, VisibilityFilter.all)
        : FilteredTodosLoading();
  }
}

I was thinking about something like this:

class FilteredTodosBloc extends Bloc<FilteredTodosEvent, FilteredTodosState> {
  final TodosBloc _todosBloc;
  StreamSubscription _todosSubscription;

  final AnotherBloc _anotherBloc;
  StreamSubscription _anotherSubscription;

  FilteredTodosBloc({@required TodosBloc todosBloc, @required AnotherBloc anotherBloc})
      : assert(todosBloc != null, anotherBloc != null),
        _todosBloc = todosBloc, _anotherBloc = anotherBloc {
    _todosSubscription = todosBloc.listen((state) {
      if (state is TodosLoaded) {
        //add(UpdateTodos((todosBloc.state as TodosLoaded).todos));
        _anotherSubscription = _anotherBloc.listen((state) {
          if (state is AnotherStateLoaded) {
            doSomething()
          }
        });
      }
    });
  }
}

But I feel that it is just bad practice, and wouldn't work as expected.

To be specific, in my database (firestore) let's say I have 3 tables:

  1. 'user' table //each user has name, email, phoneNumber, etc.
  2. 'car' table //each car has brand, model, year, etc.
  3. 'user_car' table //represents cars for user, where each entry (document) is user's id, and under that document is plain list of strings (whole table could be fetched as Map<String, List>, where key is user's id, value is list of car id's)

On home screen I wan't to show all cars for user, so logically I need to fetch all 3 tables, but 'user-car' table needs 'users' and 'cars' first, so I could provide any data from those tables, based on data from 'user-car' table.

I know I could simply put list of car id's inside user table (same hierarchy as name, email, etc), but I like the idea of decoupling everything I can. I have bad history with older firebase database, because I had to restructure whole database/backend when app grew in production :)

How would You do it?

Thanks in advance.

barzansaeedpour commented 4 years ago

@luandedeng I'll update the recipe sometime tomorrow with a section on named routes. Thanks for bringing that up 👍

Lets say i have several blocs defined above my materialApp. now when i use them in different pages and use navigator.of(context), they work fine. but how can i clear all data of those blocs when i dispose a specific page and make user enter the data once more? i mean since i defined them above materialApp, i cant dispose them. the other option here is not define them above materialApp, but then i can not use navigator.of(context) to route a new page and use blocProvider there and retrieve bloc's data (dose not have the same context).

i am really disappointed with bloc. help me pls. thanks in advance.

felangel commented 4 years ago

Hi @barzansaeedpour 👋 You should only ever provide a bloc to the subtree that needs it. If a bloc is only needed for a certain page or pages, then you should not provide it above MaterialApp. Hope that helps.

barzansaeedpour commented 4 years ago

Hi @barzansaeedpour 👋 You should only ever provide a bloc to the subtree that needs it. If a bloc is only needed for a certain page or pages, then you should not provide it above MaterialApp. Hope that helps.

thanks for quick answer. Is there any advanced example of using several bloc and exchange data between them? how can i use a blocs data in another bloc?

barzansaeedpour commented 4 years ago

Hi @barzansaeedpour 👋 You should only ever provide a bloc to the subtree that needs it. If a bloc is only needed for a certain page or pages, then you should not provide it above MaterialApp. Hope that helps.

Is there any example of not provide blocProvider above MaterialApp and use one bloc for several pages?

felangel commented 4 years ago

@barzansaeedpour check out the bloc access recipe.

luandedeng commented 4 years ago

Hi @barzansaeedpour 👋 You should only ever provide a bloc to the subtree that needs it. If a bloc is only needed for a certain page or pages, then you should not provide it above MaterialApp. Hope that helps.

Is there any example of not provide blocProvider above MaterialApp and use one bloc for several pages?

I don't know a good solution for scoped bloc in my app so far. In my practice, BLOC framework is easy to use in single page; for global usage, it' s also OK. But I don't find any good solution to share a BLOC in different pages with BlocProvider. BlocProvider shares bloc only in subtree. But when we navigate to another page from origin page, the another page is not in subtree of origin page. In this case, BlocProvider doesn't work. Is it correct? @felangel

felangel commented 4 years ago

@luandedeng please refer to the recipe linked above https://github.com/felangel/bloc/issues/74#issuecomment-572043525. It covers how to pass an existing bloc to a new route.

hunght commented 4 years ago

@felangel , in our project we are using use named routes and your suggestion, is:

JonFir commented 4 years ago

Hi @felangel

https://github.com/felangel/bloc/issues/74#issuecomment-516684320

I can't find this recipe. Could I have link? Or could you answer, why my example do not working?

class Router {
  static const words = '/';
  static const wordNew = '/word_new';

  static Route<dynamic> routerFactory(RouteSettings settings) {
    switch (settings.name) {
      case words:
        return makeWordsRoute();
      case wordNew:
        return makeWordsNewRouter();
      default:
        return MaterialPageRoute(
          builder: (_) => Scaffold(
            body: Center(child: Text('No route defined for ${settings.name}')),
          ),
        );
    }
  }

  static MaterialPageRoute makeWordsRoute() =>
      MaterialPageRoute(builder: (context) {
        return BlocProvider(
          create: (context) => WordsBloc(),
          child: WordsScreen(),
        );
      });

  static MaterialPageRoute makeWordsNewRouter() =>
      MaterialPageRoute(builder: (context) {
        return BlocProvider.value(
          value: BlocProvider.of<WordsBloc>(context), // block not fount in this context
          child: WordNew(),
        );
      });
}
felangel commented 4 years ago

@JonFir the reason yours is failing is because MaterialPageRoute's builder callback gives you a new BuildContext that is detached from the previous one. Instead, you should inject the existing BuildContext into the makeWordsNewRouter like:

static MaterialPageRoute makeWordsNewRouter(BuildContext context) =>
      MaterialPageRoute(builder: (_) {
        return BlocProvider.value(
          value: BlocProvider.of<WordsBloc>(context), // use the injected context
          child: WordNew(),
        );
      });
andreddie81 commented 4 years ago

@felangel Following the topic on this question, and, after reading you last comment to JonFir, I was wondering, once we push to the new route, how do we get the same instance of the bloc in this new route??

I am doing like this:

my main.dart:

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return BlocProvider<StatusBloc>(
        create: (context) => StatusBloc(),
        child: MaterialApp(
          debugShowCheckedModeBanner: false,
          home: HomeRoute(),
        ),
      );
  }
}

in my home_route.dart I have a method to go to a new route like this:

void _pushToDetails() async {
    final result = await Navigator.of(context).push(
      MaterialPageRoute(
        builder: (_) => BlocProvider.value(
          value: BlocProvider.of<StatusBloc>(context),
          child: DetailsRoute(),
        ),
      ),
    );
    print("---------------------> Result is $result");

  }

Not sure if I am getting the StatusBloc correctly in my DetailsRoute(), I do like this:

class DetailsRoute extends StatelessWidget {
  DetailsRoute();

  StatusBloc statusBloc;

  @override
  Widget build(BuildContext context) {
    statusBloc = BlocProvider.of<StatusBloc>(context)..add(DetailsListEvent());

    return Scaffold(...........)
felangel commented 4 years ago

@andreddie81 yup, the snippets you provided will result in the same bloc instance because you're using BlocProvider.value and performing a lookup for the existing bloc instance.

A minor comment about the last snippet: you shouldn't define non-final variables in a StatelessWidget.

class DetailsRoute extends StatelessWidget {
  DetailsRoute();

  @override
  Widget build(BuildContext context) {
    final statusBloc = BlocProvider.of<StatusBloc>(context)..add(DetailsListEvent());

    return Scaffold(...........)

Hope that helps 👍

ricpar11 commented 4 years ago

@yang-f I’ll update the docs later today with more information on that topic. You have 3 options though:

  • wrap your MaterialApp with a BlocProvider for global access
  • use named routes and setup the BlocProvider to be scoped for the named route
  • pass the bloc via constructor into the new page and then use BlocProvider to make it available in the remaining subtree
  • use your another DI library to provide the blocs

Imo it’s not a big deal to pass the bloc via constructor into the new page and then provide it again but like I said, I’ll add a new recipe for how to pass a bloc throughout the widget tree later today 👍

For those who dislikes wrapping the material app widget, or using named routes, go and try using a DI library and then just use BlocProvider.value wherever you want to provide the bloc, I think this is the best option in terms of clean code.

karanianandkumar commented 4 years ago

Is it a better idea to create Singleton of Bloc object in DI call wherever we need? @ricpar11

felangel commented 4 years ago

@karanianandkumar I would recommend using BlocProvider to provide the bloc to the part of the widget tree that needs it.

chetanjrao commented 4 years ago

I have an app, which has 3 pages that communicate with each other. I have wrapped the first widget with MultiBlocProvider Screen Shot 2020-08-03 at 1 25 43 AM

I have also wrapped the first widget with BlocConsumer and it works fine.

Screen Shot 2020-08-03 at 1 27 31 AM

In my second screen details.dart, I have wrapped it using a MultiBlocListener as your Counter and Random example

Screen Shot 2020-08-03 at 1 29 48 AM

When I move to this page using Navigator.push( ... ) from my first page, I get the following error

Screen Shot 2020-08-03 at 1 34 14 AM

chetanjrao commented 4 years ago

None of the above solutions worked. Please help me out

aigc-in-all commented 4 years ago

I was following this approach by myself, but I get

I/flutter ( 6351): The following assertion was thrown building OtherPage(dirty): I/flutter ( 6351): BlocProvider.of() called with a context that does not contain a Bloc of type CounterBloc. I/flutter ( 6351): No ancestor could be found starting from the context that was passed to I/flutter ( 6351): BlocProvider.of(). This can happen if the context you use comes from a widget above the I/flutter ( 6351): BlocProvider. I/flutter ( 6351): The context used was: I/flutter ( 6351): OtherPage(dirty)

This my target page code

import '../counter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class OtherPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    final CounterBloc _counterBloc = BlocProvider.of<CounterBloc>(context);

    return MaterialApp(
      title: 'Other Page',
      home: BlocProvider<CounterBloc>(
        bloc: _counterBloc,
        child: Center(
          child: Text(
            _counterBloc.currentState.toString(),
            style: TextStyle(fontSize: 24.0),
          ),
        ),
      ),
    );
  }

}

The named parameter 'bloc' isn't defined. Try correcting the name to an existing named parameter's name, or defining a named parameter with the name 'bloc'.

chetanjrao commented 4 years ago

I was following this approach by myself, but I get

I/flutter ( 6351): The following assertion was thrown building OtherPage(dirty): I/flutter ( 6351): BlocProvider.of() called with a context that does not contain a Bloc of type CounterBloc. I/flutter ( 6351): No ancestor could be found starting from the context that was passed to I/flutter ( 6351): BlocProvider.of(). This can happen if the context you use comes from a widget above the I/flutter ( 6351): BlocProvider. I/flutter ( 6351): The context used was: I/flutter ( 6351): OtherPage(dirty)

This my target page code

import '../counter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class OtherPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    final CounterBloc _counterBloc = BlocProvider.of<CounterBloc>(context);

    return MaterialApp(
      title: 'Other Page',
      home: BlocProvider<CounterBloc>(
        bloc: _counterBloc,
        child: Center(
          child: Text(
            _counterBloc.currentState.toString(),
            style: TextStyle(fontSize: 24.0),
          ),
        ),
      ),
    );
  }

}

The named parameter 'bloc' isn't defined. Try correcting the name to an existing named parameter's name, or defining a named parameter with the name 'bloc'.

It means your context is missing the bloc that you are trying to access You should pass an instance of CounterBloc either using

BlocProvider(
  create: (context) => CounterBloc(...),
  child: ... )

or, if you already have an existing instance of CounterBloc, you can pass using

BlocProvider.value(
  value: BlocProvider.of<CounterBloc>(context),
  child: ... )

If you are using Navigator, you can follow the navigation reciepe https://bloclibrary.dev/#/recipesflutternavigation

phyueimon162 commented 3 years ago

I have problem in changing value between pages. Lets say, 1) Page 1 have counter. I counted to 3 and go to Page 2. 2) In page2, I can access the value 3 cuz I used the global bloc as mentions above. So, I multiply 3 with 2. Then the value in bloc state become 6. My question is In bloc state it changed to 6. But not refresh in page one's Bloc builder. Why?

chetanjrao commented 3 years ago

I hope you are creating a new instance of global bloc on your second page. You need to use the existing global bloc that is referenced in your Page 1.

dgaedcke commented 3 years ago

I recommend people switch from Provider to Riverpod (also by Remi) as it solves all these problems

RaulUrtecho commented 3 years ago

Yes, thanks. but I use pushNamed and I pass the bloc like argument, but i don't know if this pass a reference or a copy :(

Navigator.of(context).pushNamed(
                            'SomeRouteName',
                            arguments: _someBloc);

and then in the pushed page

_someBloc = ModalRoute.of(context).settings.arguments;

@basketball-ico Thanks this is a doub I had if it would work since I need to fire an event of the bloc of the page from which it is navigated but it is named navigation. Now I know that I can pass the bloc as a navigation argument, it is not the cleanest but at least between two screens it works.

Enzodtz commented 3 years ago

@felangel extending this discussion a little bit further, is there a way to use blocs with auto_route package?

Also, is there a way to lazy instantiate blocs with Generated Routes? I found a solution but maybe it would make a lot of boilerplate.

Thanks

lei-cao commented 3 years ago

found this thread from auto_route to be very useful. https://github.com/Milad-Akarie/auto_route_library/issues/632

Chaitanyabsprip commented 3 years ago

what are you thoughts about using a singleton class for a bloc. That way I can put a blocprovider down a tree where I need it and not allocate the memory to that stream for the life of the application.

Muhammad-Hammad commented 3 years ago

Hi @felangel! would you be kind to tell me an easy way of migrating from getX to Flutter_bloc? something like a comparison in between them? for example, getX has "this" while in bloc, instead of "this" we use "that". so that migration might become understandable. Thank you in advance.

DongNQFinizy commented 3 years ago

hi @felangel i have a problem with navigator I have 3 page: A, B, C I used 1 bloc for all 3 pages from page A -B, i used navigator.push with blocprovider.value : Worked ok from page B-C, i used navigator.pushReplacement with blocprovider.value : not work. on page C : it said that can't find context of bloc. I think the problem was because of pushReplacement. It replace page B with page C -> So it can't find the bloc with context. How can i solve this ? Thanks :))

Navigator.pushReplacement(context, MaterialPageRoute( builder: (_) => BlocProvider.value( value: context.read(), child: OTPForgotPassScreen(), ) ) );