Can I use BlocBuilder directly in BlocProvider and have an access to a state in whole tree or should I use BlocBuilder on every widget separately if I plan to change it via state ?
Right now I have this construction on a top level and inject state to lower parts of the tree:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<NavigationCubit>(create: (context) => NavigationCubit())
],
child: BlocBuilder<NavigationCubit, NavigationState>(
builder: (context, state) {
return WillPopScope(
onWillPop: () async => false,
child: const MaterialApp(
home: RootContainer(state),
),
);
},
),
);
}
}
well yes You can but its not recommended to do it in certain way
for small project and prototype ? sure
for large project ? big no
normally You want to separate things for as much as you can because like the name of it Blocbuilder it will rebuild widgets 'INSIDE' of it
so If you wraps with that many widgets
example is like for post widget
if you tap like Iconbutton, widget that need rebuild is just iconButton right but if you wrap a entire a post it will re render in User screen in 60 fps(standard)
so if you doing that way dnt do it again, because user experience will be awful when project goes large
tldr: its like setstate with extra steps if u doing that way
Related
I just arrived on a flutter project for a web app, and all developers have a problem using flutter provider for state management.
What is the problem
When you arrive on a screen, the variables of the corresponding provider are initialised by calling a function of the provider. This function calls an api, and sets the variables in the provider.
Problem : This function is called in the build section of the widget. Each time the window is resized, the widget is rebuilt, and the function is called again.
What we want
We want to call an api when the page is first displayed, set variables with the result, and not call the api again when the widget is rebuilt.
What solution ?
We use a push from the first screen to go to the second one. We can call the function of the provider at this moment, to initialise the provider just before the second screen.
→ But a refresh on the second page will clear the provider variables, and the function to initialise them will not be called again.
We call the function to initialise the provider in the constructor of the second screen. Is it a good pattern ?
Thank you for your help in my new experience with flutter :)
I think you're mixing a couple different issues here:
How do you correctly initialize a provider
How do you call a method on initialization (only once)
For the first question:
In your main.dart file you want to do something like this:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => SomeProvider()),
ChangeNotifierProvider(create: (context) => AnotherProvider()),
],
child: YourRootWidget();
);
}
Then in a widget (that probably represents a "screen" in your app), you need to do something like this to consume state changes from that provider:
#override
Widget build(BuildContext context) {
return Container(
child: Consumer<SomeProvider>(
builder: (context, provider, child) {
return Text(provider.someState);
}
),
)
}
And you need to do something like this to get access to the provider to mutate state:
#override
Widget build(BuildContext context) {
SomeProvider someProvider = Provider.of<SomeProvider>(context, listen: false);
return Container(
child: TextButton(
child: Text('Tap me'),
onPressed: () async {
await someProvider.mutateSomeState();
}
),
)
}
Regarding the second question... You can (I think) just use the initState() method on a widget to make the call only 1 time. So...
#override
void initState() {
super.initState();
AnotherProvider anotherProvider = Provider.of<AnotherProvider>(context, listen: false);
Future.microtask(() {
anotherProvider.doSomethingElse();
});
}
If I'm off on any of that, I'm sorry. That mirrors my implementation and works fine/well.
A caveat here is that I think RiverPod is likely the place you really want to go (it's maybe easier to work with and has additional features that are helpful, etc.) but I've not migrated to RiverPod yet and do not have that figured out all the way.
Anyway... Good luck!
As far as I understood, you can wrap your application with MultiProvider and call the API before going to the second screen.
In regards to Flutter's way to rebuild widgets when navigating between routes, there is this issue: Pages on Navigator stack rebuild when a new page is pushed where the dev team and others provided these interesting insights:
About this reported behavior in the issue itself:
This is working as intended. In general, you should assume that all widgets can rebuild at any time, that they don't is mostly just an optimization. [...]
Further (re)explained here:
You should generally assume that every widget will be rebuilt every frame, and design your build methods to be idempotent [...]
When asked how to handle fetching data in build(context):
you'll need to restructure your code so that the data is not fetched again [...].
I am using BLoC to fetch remote data. For example, in my HomePage:
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocBuilder<HomeBloc, HomeState>(
bloc: sl.get()..add(const GetHomeEvent()), // `sl` from package `GetIt`, a dependency injector
builder: (context, state) {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: state.join(
(initial) => const EmptyHome(),
(loading) => const LoadingHome(),
(success) => LoadedHome(homeEntity: success.homeEntity),
(failure) => FailedHome(errorMessage: failure.message),
),
);
},
);
}
As you can read, I build a BlocBuilder and upon its instantiation, I asked the HomeBloc to fetch data:
HomeBloc() : super(HomeState.initial()) {
on<GetHomeEvent>((event, emit) async {
print('load home event request');
// code to load home and notify of result via `emit()`
);
});
The log load home event request gets printed multiple times as I navigate in and out from my home page.
How should I go about and prevent unnecessarily reloading the home ?
Should I simply cache it via a local variable?
How to handle refresh properly (e.g. hit F5 in the web browser)?
build method is such a method which will be executed frequently. For screen pages who need to fetch some data, it is prefereed to create stateful widget instead of stateless. And then you should add event on bloc inside initState method instead of build. Don’t forget to delete ..({event}) in build methos. In that way you will get rid off problem with unnecessary API requests.
I have three screens.
FirstScreen, SecondScreen and a GreenScreen,
im using custom navigation in my navigation routes like so (im using named routes),
case secondScreenUIRoute:
return Platform.isIOS
? CupertinoPageRoute(
builder: (context) => const SecondScreen())
: CustomPageRoute(
builder: (context) => const SecondScreen());
my green screen looks like so
class GreenScreen extends StatelessWidget {
const GreenScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: CustomColors.greenLight,
);
}
}
My basic idea was to have this GreenScreen in between FirstScreen and SecondScreen on navigation(from first to second), but as a fade in fade out effect. Plainly I like to give the user an impression like, when going from FirstScreen to SecondScreen, there seems to be a subtle animation where a green screen is faded in and faded out before reaching SecondScreen.
how Can i achieve this in flutter?
you can use animations package:
https://pub.dev/packages/animations#fade-through
This package contains pre-canned animations for commonly-desired effects. The animations can be customized with your content and dropped into your application to delight your users.
this is an example source code of using the package:
https://github.com/flutter/packages/tree/master/packages/animations/example
if you want to learn from youtube, you can learn here:
https://www.youtube.com/watch?v=3GMq45zRVLo
I've used this package before, and I recommend this package because it's easy to use and this package was made by the flutter team
some days ago, i raise an issue which link https://github.com/jonataslaw/getx/issues/2038#issue-1075353819
now i think there are something wrong in use getx ?
in my project i always return GetBuilder in build function, because my Scaffold has too many logic and state to use.
like this
class AppLogsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetBuilder<AppLogsLogic>(builder: (l) => Scaffold(
appBar: AppBar(
title: Text("app logs"),
),
// ... many logic and state use
),
);
}
}
but i saw the official document many times, it show me place the GetBuilder in the area where has some logic or state should be inject.
so i want to know it will make me face some problem? or not the best practice in Getx
I have a MainBloc that resides inside a main route, this route has a bottom app bar with multiple sub-routes, I want the same BLoC to run on all five sub-routes so that when one of them changes the state of the block the others will see the effect.
I tried this SO question but its really far from what I'm looking for, also I tried following what the error advised me to, but didn't work, here is the message that I got:
This can happen if:
1. The context you used comes from a widget above the BlocProvider.
2. You used MultiBlocProvider and didn't explicity provide the BlocProvider types.
Good: BlocProvider<MainBloc>(builder: (context) => MainBloc())
Bad: BlocProvider(builder: (context) => MainBloc()).
Main route:
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<MainBloc>(
builder: (BuildContext context) => MainBloc(),
),
BlocProvider<OtherBloc>(
builder: (BuildContext context) => OtherBloc(),
),
],
child: /..., //here I have the bottom app bar with 5 buttons to navigate between sub-routes
);
one of the sub-routes:
#override
Widget build(BuildContext context) {
final MainBloc bloc = BlocProvider.of<MainBloc>(context);
return /...; //here I have the context of this sub-route.
}
from what I've seen from tutorials and articles this code should work, but I can't seem to find why not.
The problem is you cannot access InheritedWidgets across routes unless you provide the InheritedWidget above MaterialApp. I would recommend wrapping your new route in BlocProvider.value to provide the existing bloc to the new route like:
Navigator.of(context).push(
MaterialPageRoute<MyPage>(
builder: (_) {
return BlocProvider.value(
value: BlocProvider.of<MyBloc>(context),
child: MyPage(),
);
},
),
);
You can find more detailed information about this in the bloc documentation
As this child has the bottom app bar:
child: /..., //here I have the bottom app bar
then I assume that the MultiBlocProvider(..) is not wrapping the whole part of app which is using this Bloc, my suggestion here is to wrap the "MaterialApp" with "MultiBlocProvider".
return MultiBlocProvider(
providers: [..],
child: MaterialApp(..) // Set MaterialApp as the child of the MultiBlocProvider
//..
)