flutter bloc streambuilder with refreshindicator rebuild twice - flutter

Widget build(BuildContext context) {
final blocData = WeatherBlocProvider.of(context).bloc;
if (WeatherBloc.permission == true) {
blocData.forceRefreshAll();
return Container(
child: StreamBuilder(
stream: blocData.zipAll,
builder: (scontext, snapshot){
//to do
}
now i am using bloc pattern with streambuilder
and when i refresh parent widget i can see blocData.forceRefreshAll() this line is requested twice.(i mean build method is requested twice) how can i make only one?
i saw unwanted rebuild subject and they said use instance or initstate but with bloc pattern i think using initstate is not possible and const value is not working with
blocData.forceRefreshAll()

build method is meant to build widget tree, it can be called multiple times for various reasons. This is why should not fetch data in build.
If you can't access bloc in initState because there is no context yet - override another method, didChangeDependencies. It's called right after initState and it can use context, so you can access bloc provider with it.

Related

Flutter BlocProvider consumption

I'm implementing a BLoC pattern for state management in my Fluter application. As I'm new in Flutter and BLoC particularly I'm evolving its usage gradually.
For new I use BLoC to communicate between two pages. One page sends an asset to the BLoC and navigates to details page. The details page uses StreamBuilder to read from the BLoC and build page with according data:
AppWidget:
Widget build(BuildContext context) => MultiProvider(
providers: [
BlocProvider(create: (context) => AssetBloc())
...
Requesting page
_onAssetMenuAction(BuildContext context, AssetMenu value, Asset asset) {
switch (value) {
case AssetMenu.validate:
var bloc = BlocProvider.of<AssetBloc>(context);
bloc.validate(asset);
Navigator.push(context,
MaterialPageRoute(builder: (context) => ValidateAssetPage()));
break;
}
Validation page
Widget build(BuildContext context) {
var bloc = BlocProvider.of<AssetBloc>(context);
Logger.root.info("Building validation page");
return StreamBuilder<AssetValidation>(
stream: bloc.outValidation,
builder: (context, snapshot) => snapshot.hasData
? QrImage.withQr(qr: snapshot.data!.qr)
: Text("No QR"));
}
BLoC
class AssetBloc extends BlocBase {
//
// Stream to handle the validation request outcome
//
StreamController<AssetValidation> _validationController =
StreamController<AssetValidation>.broadcast();
StreamSink<AssetValidation> get _inValidation => _validationController.sink;
Stream<AssetValidation> get outValidation => _validationController.stream;
//
// Stream to handle the validation request
//
StreamController<Asset> _validateController = StreamController<Asset>();
void Function(Asset) get validate => _validateController.sink.add;
//
// Constructor
//
AssetBloc([state]) : super(state) {
_validateController.stream.listen(_handleLogic);
}
void _handleLogic(Asset asset) {
_inValidation.add(AssetValidation.create(asset));
Logger.root.finest("AssetValidation instance is sent to stream");
}
void dispose() {
_validateController.close();
_validationController.close();
}
}
The problem I have is I'm getting "No QR". According to logs I see following sequence of actions:
new AssetValidation.create(): Validating asset Instance of 'Asset'
AssetBloc._handleLogic(): AssetValidation instance is sent to stream
ValidateAssetPage.build(): Building validation page
So at the moment of validation page building the validation result data should be in the stream but it seems they are not.
Unit tests of AssetBloc work as expected. So I suspect it should be related to StreamBuilder in validation page.
The StreamBuilder just shows you the last value of the stream whether the StreamBuilder was present on the current deployed widget when the stream was updated. So, if you add a new value to the stream, but the StreamBuilder is not on the current deployed widget, and, after that, you deploy the widget with the StreamBuilder, it's very likely that it won't show the updated data (in fact it shows empty data). I know, it's weird, i have the same problem when i like to use streams in that way. So, instead, i recommend you to use ValueListenable on the bloc and ValueListenableBuilder on the widget. It's very useful for that cases.
Another thing to point out is that if you're going to use just streams for the state management, it's better to use another state manager type such as provider or singleton. The reason is that, the right way to use bloc (the way you take advantage of the power of bloc) is using just the method add() for the events and logic, and using the established bloc State classes to show and update the data with the BlocBuilder on the widget.

Flutter Bloc change state from different widget

On my apps home page, I have a list view that rebuilds whenever the user clicks on a new page.
I have implemented the block pattern using the flutter_bloc plugin but I don't know how to change the state from another widget.
Two things you will have to keep in mind for changing state with flutter bloc:
Dependency injection (DI) of a bloc.
Interaction with your bloc instance.
Dependency injection of a bloc
Case 1. You need to provide bloc to widget subtree within one route.
To provide a single instance of a bloc to multiple widgets within a subtree you use BlocProvider widget. It creates bloc instance, automatically disposes of it when needed and provides bloc to its children via BlocProvider.of<T>(context), where T is the name of your bloc:
BlocProvider(
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
Keep in mind, that by deafult it is created with property lazy: true, means that create: (BuildContext context) => BlocA(), will be executed after invoke of BlocProvider.of<T>(context). If you dont want it - set lazy: false in advance.
Case 2. You need to provide bloc to widgets from another route (to another context).
BlocProvider automatically disposes of a bloc instance with context of new route instantiated, but that will not happen if you use BlocProvider.value:
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: ScreenA(),
);
Important note: BlocProvider.value should only be used for providing existing instances to new subtree, do not create Bloc instance with it
Interaction with your bloc instance
Starting from bloc v6.1.0 context.bloc and context.repository are deprecated in favor of context.read and context.watch.
context.select allows to update UI based on a part of a bloc state:
final name = context.select((UserBloc bloc) => bloc.state.user.name);
context.read access a bloc with a BuildContext and do not result rebuilds.
context.watch gets a value from the nearest ancestor provider of its type and subscribes to the provider.
To access the bloc's state
If you need a widget rebuilding due bloc value changing use context.watch or BlocBuilder:
// Using context.watch at the root of the build method will result in the entire widget being rebuilt when the bloc state changes.
#override
Widget build(BuildContext context) {
final state = context.watch<MyBloc>().state;
return Text('$state');
}
or with BlocBuilder:
// If the entire widget does not need to be rebuilt, either use BlocBuilder to wrap the parts that should rebuild
#override
Widget build(BuildContext context) {
return BlocBuilder<MyBloc, MyState>(
builder: (context, state) => Text('$state'),
);
}
To access the bloc so that an event can be added
Use context.read:
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => context.read<MyBloc>().add(MyEvent()),
...
)
}
You can place the BlocProvider to a common ansestor like as a parent of your MaterialApp widget, or you can pass the bloc to your new page like this:
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: ScreenB(),
);

Flutter - How to dynamically add height value of a container before loading the UI?

I have added a setState Method inside the build widget after getting my data from API response via StreamBuilder. But it gives this error:
Unhandled Exception: setState() or markNeedsBuild() called during build.
How do I avoid this situation?
My code:
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: StreamBuilder(
stream: bloc.getData,
builder: (context, AsyncSnapshot<Home> dataSnapshot) {
if (dataSnapshot.hasData) {
if (dataSnapshot.data.description != null) _expandHeightBy(40);
........
Function
_expandHeightBy(double increase) async {
setState(() {
_expandedHeightVal += increase;
});
}
It is not possible to call setState during build, and for good reason. setState runs the build method. Therefore if you were able to call setState in build, it would infinitely loop the build method since it essentially calls itself.
Check out this article. It has an example of conditionally rendering based on a StreamBuilder. https://medium.com/#sidky/using-streambuilder-in-flutter-dcc2d89c2eae
Removed the setState from the method calling as #CodePoet suggested and it actually worked fine.
_expandHeightBy(double increase) {
_expandedHeightVal += increase;
}

Dart Provider: not available at the immediate child

#override
Widget build(BuildContext context) {
return BlocProvider<HomeBloc>(
create: (context) {
return HomeBloc(homeRepo: HomeRepository());
},
child: BlocProvider.of<HomeBloc>(context).state is HomeStateLoading
? CircularProgressIndicator()
: Container());
}
I am confused with the error:
BlocProvider.of() called with a context that does not contain a Bloc of type HomeBloc.
No ancestor could be found starting from the context that was passed to
BlocProvider.of<HomeBloc>().
Didn't I just create the HomeBloc at its immediate parent? What does it want?
You are using the context passed into the build method of your widget class to look for a parent BlocProvider. However, that context is the widget tree as far as your widget class sees it. Because of this, your BlocProvider.of is looking for a BlocProvider that is a parent of your widget class. If you want to get the provider that is the immediate parent, you need a new context object in which the BlocProvider is an ancestor in the widget tree. The easiest way to do this is with a Builder widget:
#override
Widget build(BuildContext context) {
return BlocProvider<HomeBloc>(
create: (context) {
return HomeBloc(homeRepo: HomeRepository());
},
child: Builder(
builder: (newContext) => BlocProvider.of<HomeBloc>(newContext).state is HomeStateLoading
? CircularProgressIndicator()
: Container(),
),
);
}
That being said, it's pretty redundant to create a provider and then immediately reverence the provider. Providers are for retrieving stuff further down the widget tree, not typically for immediate descendants. In this case, using a provider is overkill and there isn't really any reason to not just have the bloc be a field of your class and reference it directly.
From the documentation :
The easiest way to read a value is by using the static method
Provider.of(BuildContext context).
This method will look up in the widget tree starting from the widget
associated with the BuildContext passed and it will return the nearest
variable of type T found (or throw if nothing is found).
In your case it starts looking up the widget tree from your whole widget (associated to the BuildContext).
So you need to move your BlocProvider to be an ancestor of this widget.
If for some reason this is not possible, you can use Consumer, which allows obtaining a value from a provider when you don't have a BuildContext that is a descendant of the said provider.
Read https://pub.dev/documentation/provider/latest/provider/Consumer-class.html

BLoCs and multiple streams - Is there a better solution?

Currently I'm working with BLoCs in Flutter and I've got a question about multiple streams within a Bloc.
For example when a screen has got multiple widgets which should depend on the Bloc. I could wrap the whole screen in the StreamBuilder, but then every time all widgets would be rebuilt.
The example bloc:
class TestBloc {
final StreamController _dataController = StreamController<String>();
final StreamController _appBarTitleController = StreamController<String>();
TestBloc();
Stream<String> get appBarTitle => _appBarTitleController.stream;
Stream<DataState> get data => _dataController.stream;
void fetchData(String path) async {
_dataController.sink.add(PokemonDataLoading());
Data data = await _getData();
_dataController.sink.add(Loaded(data));
_appBarTitleController.sink.add(data.name);
}
Future<Data> _getData(String path) async {
return await _dataRepository.fetchData(path);
}
void dispose() {
_dataController.close();
_appBarTitleController.close();
}
}
On the example build method you can see two different StreamBuilders, one for the app bar title and one for the content. Of course I could wrap them in this example into one StreamBuilder, but sometimes this isn't easily possible. They may depend on other data or user interactions.
#override
Widget build(BuildContext context) {
_testBloc.fetchData();
return ScaffoldWithSafeArea(
title: StreamBuilder(
stream: _testBloc.appBarTitle,
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data);
}
return Text("Test");
},
),
child: StreamBuilder<DataState>(
stream: _testBloc.data,
builder: (context, AsyncSnapshot<DataState> snapshot) {
DataState state = snapshot.data;
if (state is DataInitial) {
return _buildLoading();
} else if (state is DataLoaded) {
return _buildContent(state.data);
}
return _buildLoading();
},
),
);
}
Is there maybe a better solution for multiple Streams on one screen? I use a lot of boilerplate code here and would like to avoid this.
In order to manage multiple streams in one screen, the best solution is to have multiple widgets that listen the corresponding stream.
This way, you increase the performance of your app by optimizing the total number of builds of your widgets.
By doing this, you can create widgets that listen an output (stream) of your BLoC and reuse them in different parts of your app, but in order to make the widget reusable you need to inject the BLoC into the widget.
If you see the BLoC UI design guidelines
Each "complex enough" component has a corresponding BLoC
This way your screen will now be composed of different components, and this component is a widget that listens to an output (stream) of your BLoC.
So you are doing things right.
If you want to reduce the repetitive code a bit in your widgets, you can:
Create your own widget that listens to the stream and directly returns the output of the BLoC (in your case you call state), this way you don't need to use snapshot.data like in the StreamBuilder. The example of this widget is the BlocBuilder of flutter bloc library.
Use the flutter bloc library that has widgets that reduce the boilerplate code when use BLoC pattern, but if you use this library, you now need to create your BLoCs using bloc library, but if you do this, now you reduce the boilerplate code of creating StreamControllers in your BLoC, and other interesting features, so you should take a look the power of bloc and flutter bloc libraries.