Describe the bug
I pass the BlocProvider down from my main method and I can see in my devtools that it's attached. However setting a breakpoint inside the BlocBuilder I see that the inside of it is never called. Not even a print('test') will execute inside of my BlocBuilder on initial screen mount. I've tried just about every ticket or article and can't figure out why this is happening. Any help is appreciated!
Debugging things I've seen:
Expected behavior
A clear and concise description of what you expected to happen.
Screenshots
If applicable, add screenshots to help explain your problem.
*Logs *
Run flutter analyze and attach any output of that command below.
If there are any analysis errors, try resolving them before filing this issue.
Paste the output of running flutter doctor -v here.
Additional context
Add any other context about the problem here.
Hi @shamilovtim 馃憢
Thanks for opening an issue!
Can you please share a link to a sample app which illustrates the issue you're facing so I can debug it locally? Thanks 馃憤
Hi @felangel. Is there a suggested tool for sharing the code with you? Something similar to Expo Snack maybe?
@shamilovtim can you create a github repository?
Hey @felangel I figured out what's causing it. My BlocBuilder is in a SliverList: delegate: SliverChildBuilderDelegate(). As soon as I moved it out of the SliverList it worked. Do you know what the rule of thumb is for using Bloc with dynamic lists like this? The relevant code is below.
List<Widget> buildCycleTab() {
return [
SliverSafeArea(
top: false,
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
BlocBuilder<CyclesBloc, CyclesState>(
builder: (context, state) {
print('test');
print(state);
if (state is CyclesInitial) {
return buildInitial();
}
if (state is CyclesLoading) {
return buildLoading();
}
if (state is CyclesLoaded) {
return Container(
color: Color(0xffeaf1f6),
child: Column(
children: <Widget>[
TrendsCyclesChart(state.cycles[index]),
SizedBox(height: 12),
],
),
);
} else if (state is CyclesError) {
return Text('There was an error');
} else {
print('reached end');
return Text('Nothing');
}
},
);
},
childCount: 3,
),
),
)
];
}
Solution:
Moved BlocBuilder to the top of my build method and put the state into a variable. That way I can use the state elsewhere outside of BlocBuilder. Not sure if I'm going to need setState but haven't noticed if I need it yet.
class TrendsScreenState extends State<TrendsScreen> {
int currentSegment = 0;
var _state;
@override
Widget build(context) {
return BlocBuilder<CyclesBloc, CyclesState>(
builder: (context, state) {
_state = state;
return ...
}
...
}
Glad you figured it out but I think it鈥檚 worth noting you shouldn鈥檛 need to store state as a private member variable. Just wrap the parts of you build that need access to state in a BlocBuilder and you should be good 馃憤
@felangel That's what I'd normally do but in this instance but the issue here is 1) Slivers and 2) individual build methods make this difficult. Are you suggesting using multiple blocbuilders and/or passing state around as a parameter rather than storing it in variable?
You can either have one BlocBuilder at the root of your build method and you don鈥檛 need to track state internally in the widget or if you want more fine-grain control only wrap the widgets that need to update in their own BlocBuilders (you can use conditions as well to optimize how often they rebuild).
Hey @felangel could you clarify something for me? I wasn't sure if you were implying in your second to last message that storing the state of a Bloc in a variable of a stateful widget is a bad thing:
you shouldn鈥檛 need to store state as a private member variable
Is there any particular reason for that? Because it stops us from using pure functions?
I've moved forward by passing the state around as a parameter. e.g.:
BlocBuilder((state)
BuildWidgetA((state)
BuildWidgetA1((state)
BuildWidgetA1-1(state)
)
)
The reason I didn't want to keep using BlocBuilders everywhere is that it would require me to test the state everywhere to see if it's loaded. This would result in a lot of extra and useless code in deeply nested methods. If I pass it as a parameter then I know in a deeply nested method that it could never be an errorstate or a loadingstate because the parent method that called it was already a reaction to a loadedstate.
In other tools I am not used to passing parameters deeply like this. Usually I would use a provider, or in the case of Redux I can get the stateful variable anywhere I want with useSelector. Is there no simple way using BloC of simply reaching out to the global state and saying "give me this property" without all of the parameter drilling?
Hey @shamilovtim you can definitely achieve that but you'll likely need to refactor your state. Rather than having something like:
abstract class MyState {}
class Initial extends MyState {}
class Loading extends MyState {}
class Success extends MyState {
Success(this.data);
final data;
}
You can represent your state like:
enum Status { initial, loading, success, failure }
class MyState {
MyState({this.data, this.status = Status.initial});
final data;
final Status status;
}
Then you can access the data via
context.bloc<MyBloc>().state.data;
Hope that helps 馃憤
Thanks for the explanation Felix I think I'm going to refactor to use the internal variable. Looks very clean.