Greetings, I have a mystical problem with rebuilding widget tree. BlocBuilder build only InitialState and don't handle yield a new states from bloc component.
flutter_bloc: ^2.0.0
Bloc implementation
class SearchBloc extends Bloc<SearchEvent, SearchState> with SaleMapBlocMixin {
@override
SearchState get initialState => InitialDataSearchState();
@override
Stream<SearchState> transformEvents(
Stream<SearchEvent> events,
Stream<SearchState> Function(SearchEvent event) next,
) {
return super.transformEvents(
(events as Observable<SearchEvent>).debounceTime(
Duration(milliseconds: 500),
),
next,
);
}
@override
Stream<SearchState> mapEventToState(
SearchEvent event,
) async* {
if (event is GetSearchDataSearchEvent) {
yield* getSearchDataGenerator(event);
}
}
Stream<SearchState> getSearchDataGenerator(
GetSearchDataSearchEvent event) async* {
yield LoadingDataSearchState();
QueryResult searchResult =
await _fetchSearchResults(event.searchText, event.cameraBounds);
if (searchResult.hasErrors)
yield ErrorLoadingSearchState(searchResult.errors.toString());
List<CampaignVenue> campaignVenues =
_getCampaignVenues(searchResult.data["campaignVenues"]);
print(campaignVenues.toString());
if (campaignVenues.isNotEmpty){
LocationData currentLocation = await getCurrentLocation();
yield LoadedDataSearchState(campaignVenues, currentLocation);
}
else
yield EmptyDataSearchState();
}
Future<QueryResult> _fetchSearchResults(
String searchText, LatLngBounds cameraBounds) async {
final QueryResult searchResult =
await getResponse(QueryOptions(document: searchQuery, variables: {
"text": "$searchText",
"track_search": true,
"in_bounding_box": {
"south_west": {
"lat": cameraBounds.southwest.latitude,
"lng": cameraBounds.southwest.longitude
},
"north_east": {
"lat": cameraBounds.northeast.latitude,
"lng": cameraBounds.northeast.longitude
}
}
}));
return searchResult;
}
List<CampaignVenue> _getCampaignVenues(data) =>
data.map<CampaignVenue>((item) => CampaignVenue.fromJson(item)).toList();
}
Page implemenatation
class SearchPage extends StatefulWidget {
final LatLngBounds cameraBounds;
const SearchPage({Key key, this.cameraBounds}) : super(key: key);
@override
_SearchPageState createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
BlocProvider.of<SearchBloc>(context).close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
builder: (context) => SearchBloc(),
child: Scaffold(body: BlocBuilder<SearchBloc, SearchState>(
builder: (BuildContext context, SearchState state) {
List<Widget> slivers = [
SearchHeader(cameraBounds: widget.cameraBounds)
];
if (state is InitialDataSearchState) slivers.add(_buildInitial());
if (state is LoadingDataSearchState) slivers.add(_buildLoading());
if (state is ErrorLoadingSearchState) slivers.add(_buildInitial());
if (state is EmptyDataSearchState) slivers.add(_buildEmptyResult());
if (state is LoadedDataSearchState)
slivers = <Widget>[
...slivers,
..._buildData(state.campaignVenues, state.currentLocation)
];
return CustomScrollView(
slivers: slivers,
);
},
)),
);
}
Widget _buildEmptyResult() => SliverFillRemaining(
child: Center(
child: Container(
child: Text("No search result"),
),
),
);
List<Widget> _buildData(
List<CampaignVenue> campaignsVenues, LocationData currentLocation) {
return <Widget>[
_buildStickyHeaderList(1, campaignsVenues, currentLocation),
_buildStickyHeaderList(2, campaignsVenues, currentLocation)
];
}
_buildStickyHeaderList(
index, List<CampaignVenue> campaignsVenues, currentLocation) {
return SliverStickyHeaderBuilder(
builder: (context, state) => _buildAnimatedHeader(context, index, state),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) {
return ListTile(
title: _buildLists(index, i, campaignsVenues, currentLocation),
);
},
childCount: campaignsVenues.length,
),
),
);
}
Widget _buildLists(int sliverIndex, int i,
List<CampaignVenue> campaignsVenues, currentLocation) {
if (sliverIndex == 1)
return CategoryDetailCampaignCard(
currentLocation: currentLocation,
campaignVenue: campaignsVenues[i],
);
else if (sliverIndex == 2)
return CategoryDetailVenueCard(
currentLocation: currentLocation,
campaignVenue: campaignsVenues[i],
);
}
Widget _buildAnimatedHeader(
BuildContext context, int index, SliverStickyHeaderState state) {
return new Container(
height: 60.0,
color: SaleMapThemeColors.white.withOpacity(1.0 - state.scrollPercentage),
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: Container(
margin: EdgeInsets.only(top: 16),
child: Row(
children: <Widget>[
Text(
index == 1 ? "Campaigns" : "Venues",
style: TextStyle(
color: Colors.black, fontSize: 20, fontFamily: "Roboto"),
),
],
),
),
);
}
Widget _buildInitial() => SliverFillRemaining(
child: Center(
child: Container(
child: Text("Enter search data"),
),
),
);
Widget _buildLoading() => SliverFillRemaining(
child: Center(
child: Container(
child: SaleMapCircularProgressIndicator(),
),
),
);
}
Console Logs
flutter: BlocBuilder InitialDataSearchState
flutter: GetSearchDataSearchEvent
flutter: Transition { currentState: InitialDataSearchState, event: GetSearchDataSearchEvent, nextState: LoadingDataSearchState }
flutter: [Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue']
flutter: Transition { currentState: LoadingDataSearchState, event: GetSearchDataSearchEvent, nextState: LoadedDataSearchState }
flutter: GetSearchDataSearchEvent
flutter: GetSearchDataSearchEvent
flutter: Transition { currentState: LoadedDataSearchState, event: GetSearchDataSearchEvent, nextState: LoadingDataSearchState }
flutter: [Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue', Instance of 'CampaignVenue']
flutter: Transition { currentState: LoadingDataSearchState, event: GetSearchDataSearchEvent, nextState: LoadedDataSearchState }
flutter: GetSearchDataSearchEvent
flutter: GetSearchDataSearchEvent
flutter: GetSearchDataSearchEvent
flutter: Transition { currentState: LoadedDataSearchState, event: GetSearchDataSearchEvent, nextState: LoadingDataSearchState }
flutter: []
flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[鉁揮 Flutter (Channel unknown, v1.9.1+hotfix.4, on Mac OS X 10.15.1 19B88, locale
en-US)
[鉁揮 Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[鉁揮 Xcode - develop for iOS and macOS (Xcode 11.2)
[鉁揮 Android Studio (version 3.5)
[!] Connected device
! No devices available
! Doctor found issues in 1 category.
Hi @IgorChicherin 馃憢
Thanks for opening an issue!
I'm guessing you're extending Equatable in your state classes and are not properly overriding the props
getter. Can you please share your SearchState
implementation? Thanks!
@felangel Yep, sure, here is states implementation.
abstract class SearchState extends Equatable {
const SearchState();
}
class InitialDataSearchState extends SearchState {
@override
List<Object> get props => ["InitialDataSearchBarState"];
}
class LoadingDataSearchState extends SearchState {
@override
List<Object> get props => ["LoadingDataSearchBarState"];
}
class LoadedDataSearchState extends SearchState {
final List<CampaignVenue> campaignVenues;
final LocationData currentLocation;
LoadedDataSearchState(this.campaignVenues, this.currentLocation);
@override
List<Object> get props => ["LoadedDataSearchBarState"];
}
class EmptyDataSearchState extends SearchState {
@override
List<Object> get props => ["EmptyDataSearchState"];
}
class ErrorLoadingSearchState extends SearchState {
final String errorMessage;
ErrorLoadingSearchState(this.errorMessage);
@override
List<Object> get props => ["ErrorLoadingSearchBarState"];
}
props
should return the properties for each class otherwise Equatable cannot do the equality comparison properly.
abstract class SearchState extends Equatable {
const SearchState();
@override
List<Object> get props => [];
}
class InitialDataSearchState extends SearchState {}
class LoadingDataSearchState extends SearchState {}
class LoadedDataSearchState extends SearchState {
final List<CampaignVenue> campaignVenues;
final LocationData currentLocation;
const LoadedDataSearchState(this.campaignVenues, this.currentLocation);
@override
List<Object> get props => [campaignVenues, currentLocation];
}
class EmptyDataSearchState extends SearchState {}
class ErrorLoadingSearchState extends SearchState {
final String errorMessage;
const ErrorLoadingSearchState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}
Also you need to ensure that CampaignVenue
and LocationData
extend Equatable
and override props
correctly.
Hope that helps 馃憤
@felangel oh, my bad, thanks alot for helping
Most helpful comment
@felangel oh, my bad, thanks alot for helping