I am trying to learn and use flutter bloc in other to i created a project that listen to user location and also get some coordinates from service and show this coordinates in map as a marker. So this is myApp method:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: MultiBlocProvider(providers: [
BlocProvider<FoursquareBloc>(create: (context) => sl<FoursquareBloc>()), //bloc page to get corrdinate
BlocProvider<LocationBloc>(create: (context) => sl<LocationBloc>()), // bloc page to listen to change user location
], child: HomeFoursquareScreen()),
);
}
}
HomeFoursquareScreen page shows map:
Widget build(BuildContext context) {
return BlocBuilder<LocationBloc, LocationState>(builder: (context, state) {
if (state is LocationLoadingState) {
return Center(child: CircularProgressIndicator());
} else if (state is LocationLoadedState) {
print("LocationLoadedState");
intMapCoordinate =
LatLng(state.location.latitude, state.location.longitude);
} else if (state is UserLocationListeningState) {
_controller.move(
LatLng(state.location.latitude, state.location.longitude), 15.0);
}
return FlutterMap(
mapController: _controller,
options: MapOptions(
center: intMapCoordinate,
zoom: 13.0,
),
layers: [
TileLayerOptions(
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c']),
],
);
});
}
I am confused because i don't know how could i use FoursquareBloc to get coordinates to showing as a marker in the map?
This is LocationBloc
class LocationBloc extends Bloc<LocationEvent, LocationState> {
var location = Location();
StreamSubscription<LocationData> _locationSubscription;
LocationData currentLocation;
#override
LocationState get initialState => LocationLoadingState();
#override
Stream<LocationState> mapEventToState(
LocationEvent event,
) async* {
if (event is GetCurrentLocation) {
yield* _mapToGetCurrentLocationState();
} else if (event is StartListeningForLiveLocation) {
yield* _mapToStartListeningUserLocation();
}else if (event is AddLiveUserLocation) {
yield UserLocationListeningState(event.location);
}
}
and this is FoursquareBloc:
class FoursquareBloc extends Bloc<FoursquareEvent, FoursquareState> {
final FoursquareRepository repo;
FoursquareBloc(this.repo);
#override
FoursquareState get initialState => Empty();
#override
Stream<FoursquareState> mapEventToState(
FoursquareEvent event,
) async* {
if (event is GetVenuesByUserEvent) {
yield Loading();
try {
final result = await repo.getVenuesByUser(event.lat, event.lng);
yield Loaded(result);
} on Exception catch (e) {
yield Error(e.toString());
}
}
}
}
Does it again call BlocBuilder under BlocBuilder<LocationBloc, LocationState>(builder: (context, state) or i have to call FoursquareBlo inside LocationBloc by using LocationBloc constructor?
I had same issue and until now Flutter Bloc library does not support MultiBlocBuilder. you can use nested bloc builder or Create a new bloc which combines states from the other two. I make an issue in Github check it for more information.
Related
With a state change observer I can see that my bloc changes its states to LoadInProgress and back to PmLoadSuccess. However, the widget is not called with the change to LoadInProgress. I have a breakpoint in the BlocBuilder, however, the code stops there only when PmLoadSuccess is emitted.
My state definition
#freezed
class ProblemManagerState with _$ProblemManagerState {
const factory ProblemManagerState.initial() = PmInitial;
const factory ProblemManagerState.loadInProgress() = PmLoadInProgress;
const factory ProblemManagerState.loadSuccess(
Problem problem) = PmLoadSuccess;
) = PmSyncFailed;
}
The bloc
#injectable
class ProblemManagerBloc extends Bloc<ProblemManagerEvent, ProblemManagerState> {
final IProblemsRepository problemsRepository;
ProblemManagerBloc(this.problemsRepository) : super(PmInitial());
#override
Stream<ProblemManagerState> mapEventToState(
ProblemManagerEvent event,
) async* {
yield PmLoadInProgress(); // <<< does not emit to widget
yield* event.map(
pmProblemRequested: (e) async* {
yield ProblemManagerState.loadInProgress(); // <<< doesn't emit either
print (state);
yield* _mapPmProblemRequestedToState(e);
},
);
}
Stream<ProblemManagerState> _mapPmProblemRequestedToState(/*PmProblemRequested*/dynamic event) async* {
dynamic nextProblem;
late int problemClassId;
problemClassId = event.problemClassId!;
}
final String rootId = await _getNextProblemRootId(problemClassId);
final Either<StorageFailure, Problem> failureOrProblem = await problemsRepository.getProblem(rootId);
yield failureOrProblem.fold(
(fail) => PmLoadFailure(fail),
(load) => PmLoadSuccess(load)); // <<< yield does emit to widget
}
The widget (calling another widget if load successful)
class ProblemManagerPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ProblemManagerBloc, ProblemManagerState>(
builder: (context, state) {
return state.map( // <<< breakpoint not reached with PmLoadInProgress (=ProblemManagerState.loadInProgress)
initial: (_) => Container(),
loadInProgress: (_) => const Center(
child: CircularProgressIndicator(),
),
loadSuccess: (state) => PmGateway(state.problem),
);
}
);
}
}
Taken from github issue list
This is expected because BlocBuilder can only rebuild at 60fps. In this case you are emitting states faster than BlocBuilder can rebuild.
I am facing one issue in which when I am going from one page to another page using flitter BLoC, my first page rebuild before reaching to second. I am able to restrict the rebuild of the page using buildWhen in BlocBuilder, but the problem is when I come back to the first page again by back press then the page can not show the previous state widgets. I don't know how to manage navigation between pages without rebuild the page again, I am using flutter_bloc 6.1.1 below is my code.
FirstPage
class FirstPage extends StatefulWidget {
final MyData dataObj;
FirstPage({this.dataObj});
#override
_MyFirstPageState createState() => _MyFirstPageState();
}
class _MyFirstPageState extends State<FirstPage> {
FirstPageBloc _bloc = FirstPageBloc();
String _userAddress='';
#override
void initState() {
super.initState();
_bloc.add(UserInfoEvent(dataObj:widget.dataObj));
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text(StringConstants.APP_TITLE_HEADING),
),
body: BlocListener<FirstPageBloc, FirstPageState>(
cubit: _bloc,
listenWhen: (previousState, state) {
// return true/false to determine whether or not
// to call listener with state
return true;
},
listener: (context, state) async{
if (state is LoadingState) {
print('Loading ...');
}
if (state is DataInfoState) {
_userAddress=state.userAddress;
}
if(state is ConfirmationState){
Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage(dataObj: widget.dataObj)));
}
},
child: BlocBuilder<FirstPageBloc, FirstPageState>(
//bloc: _bloc,
cubit: _bloc,
buildWhen: (previousState, state) {
// return true/false to determine whether or not
// to rebuild the widget with state
if(state is ConfirmationState){
return false;
}
return true;
},
builder: (context, state) {
if (state is LoadingState) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(valueColor:
AlwaysStoppedAnimation<Color>(ColorConstants.Primary),),
Text(StringConstants.PLEASE_WAIT)
],),
);
}
return _mainWidget();
}),
),
);
}
}
BLoC
class FirstPageBloc extends Bloc<FirstPageEvent, FirstPageState>{
FirstPageBloc() : super(InitialState());
#override
Stream<FirstPageState> mapEventToState(FirstPageEvent event) async*{
// TODO: implement mapEventToState
if(event is DataInfoEvent){
yield* _getUserData(event.dataObj);
}
if(event is ConfirmationEvent){
yield* _confirmTaskData(event.dataObj);
}
}
Stream<DelConfirmState> _confirmTaskData(MyData dataObj) async* {
yield LoadingState();
//Performing some SQLite DB operations
yield ConfirmationState();
}
Stream<DelConfirmState> _getUserData(MyData dataObj) async* {
yield LoadingState();
String userAddress='ABDC001, PIN- 0091910, 5th Main USA';
//Fetching User data from SQLite database and passing to UI
yield DataInfoState(userAddress:userAddress);
}
}
State
abstract class FirstPageState extends Equatable {}
///This is our initial state
class InitialState extends FirstPageState {
#override
List<Object> get props => null;
}
//This state will call for loading the progress var
class LoadingState extends FirstPageState {
#override
List<Object> get props => [];
}
//This state will call for loading the progress var
class ErrorState extends FirstPageState {
final String errorMessage;
ErrorState({#required this.errorMessage});
#override
List<Object> get props => [];
}
//This state will retun the userdata
class DataInfoState extends FirstPageState {
final String userAddress;
DataInfoState({#required this.userAddress});
#override
// TODO: implement props
List<Object> get props => [];
}
class TaskConfirmationState extends FirstPageState {
ConfirmationState({});
#override
// TODO: implement props
List<Object> get props => [];
}
Event
abstract class FirstPageEvent extends Equatable {}
class GetUserInfoEvent extends FirstPageEvent {
final MyData dataObj;
GetUserInfoEvent({this.taskObj});
#override
List<Object> get props => [];
}
class ConfirmationEvent extends FirstPageEvent {
final MyData dataObj
ConfirmationEvent({this.dataObj});
#override
List<Object> get props => [];
}
Please advise
Thank You
You need to provide your bloc at a higher level widget, then you need to get it from the context. In this way the state will persist even through navigation.
You can do that wrapping your widget like this:
BlocProvider(
create: (context) => FirstPageBloc(),
child: FirstPage(),
)
and then inside of initState you can get it like this:
_bloc = BlocProvider.of<FirstPageBloc>(context);
I'm creating an app where you login and go to a page where you have a list of your restaurants, you have also a form where you can add a new restaurant.
This part works.
The problem is that when i click add the restaurant is added in firestore correctly, but the list doesn't refresh. I usually yield 2 states, a LoadingState and a LoadedRestaurantsListState, but with the last version of flutter_bloc this trick doesn't work, seems like just the last state yielded is received, but the previous was LoadedRestaurantsListState, so they are equals and the blocbuilder ignores the second one. So I've to use the equatable's props to distinguish the 2 states, but in the equatable documentation is written: "Note: Equatable is designed to only work with immutable objects so all member variables must be final".
So I've to make all the model's fields final, but if I do it how can i modify just one o two fields when I need it to?
What is the best practice?
If someone has examples, or videos, etc it would be very appreciated.
Thanks in advance
Without props
FirebaseBloc.dart
Stream<FirebaseState> mapEventToState(
FirebaseEvent event,
) async* {
print("event firebase ${event.runtimeType.toString()}");
if (event is CreateRestaurantFirebaseEvent) {
yield LoadingState();
await _databaseService.createRestaurant(event.restaurant, event.user);
List<Restaurant> restaurantsList = await _databaseService
.loadRestaurantsList(event.user.restaurantsIDsList);
yield LoadedRestaurantsListState(restaurantsList);
}
if (event is LoadRestaurantsListEvent) {
List<Restaurant> restaurantsList =
await _databaseService.loadRestaurantsList(event.restaurantsIDs);
yield LoadedRestaurantsListState(restaurantsList);
}
FirebaseState.dart
class LoadingState extends FirebaseState {
#override
List<Object> get props => [];
}
class LoadedRestaurantsListState extends FirebaseState {
List<Restaurant> restaurantsList;
LoadedRestaurantsListState(this.restaurantsList);
#override
List<Object> get props => [];
}
view.dart
class RestaurantSelectionScreen extends StatefulWidget {
final User user;
RestaurantSelectionScreen({
#required this.user,
});
#override
_RestaurantSelectionScreenState createState() =>
_RestaurantSelectionScreenState();
}
class _RestaurantSelectionScreenState extends State<RestaurantSelectionScreen> {
FirebaseBloc _firebaseBloc;
#override
void initState() {
super.initState();
_firebaseBloc = FirebaseBloc();
_firebaseBloc.add(LoadRestaurantsListEvent(widget.user.restaurantsIDsList));
}
#override
Widget build(BuildContext context) {
return BlocProvider<FirebaseBloc>(
create: (context) => _firebaseBloc,
child: Scaffold(
body: SingleChildScrollView(
child: Center(
child: BlocBuilder(
cubit: _firebaseBloc,
builder: (context, state) {
print("state ${state.runtimeType.toString()}");
if (state is InitialFirebaseState) {
return CircularProgressIndicator();
} else if (state is LoadedRestaurantsListState) {
return buildUI(state);
} else if (state is LoadingState) {
return CircularProgressIndicator();
} else {
return _CreateRestaurantFormWidget(widget.user);
}
},
),
),
),
),
);
}
Recently I am learning flutter_bloc, and I refer to the project flutter_weather.
What I am puzzled is that if a Bloc class has many Events, and most of the Events will have values returned by State, and there are many BlocBuilders in the project, what should I do if I want a BlocBuilder to only respond to a certain Event?
The method I can think of is to divide this Bloc into multiple Blocs, or treat each value to be returned as an attribute of Bloc, BlocBuilder uses the buildwhen method to determine whether to rebuild.
But both of these methods are not good for me. Is there any good method? It is best to have projects on github for reference.
For example:
This is Event:
abstract class WeatherEvent extends Equatable {
const WeatherEvent();
}
class WeatherRequested extends WeatherEvent {
final String city;
const WeatherRequested({#required this.city}) : assert(city != null);
#override
List<Object> get props => [city];
}
class WeatherRefreshRequested extends WeatherEvent {
final String city;
const WeatherRefreshRequested({#required this.city}) : assert(city != null);
#override
List<Object> get props => [city];
}
This is State:
abstract class WeatherState extends Equatable {
const WeatherState();
#override
List<Object> get props => [];
}
class WeatherInitial extends WeatherState {}
class WeatherLoadInProgress extends WeatherState {}
class WeatherLoadSuccess extends WeatherState {
final Weather weather;
const WeatherLoadSuccess({#required this.weather}) : assert(weather != null);
#override
List<Object> get props => [weather];
}
class WeatherLoadFailure extends WeatherState {}
This is Bloc:
class WeatherBloc extends Bloc<WeatherEvent, WeatherState> {
final WeatherRepository weatherRepository;
WeatherBloc({#required this.weatherRepository})
: assert(weatherRepository != null),
super(WeatherInitial());
#override
Stream<WeatherState> mapEventToState(WeatherEvent event) async* {
if (event is WeatherRequested) {
yield* _mapWeatherRequestedToState(event);
} else if (event is WeatherRefreshRequested) {
yield* _mapWeatherRefreshRequestedToState(event);
}
}
Stream<WeatherState> _mapWeatherRequestedToState(
WeatherRequested event,
) async* {
yield WeatherLoadInProgress();
try {
final Weather weather = await weatherRepository.getWeather(event.city);
yield WeatherLoadSuccess(weather: weather);
} catch (_) {
yield WeatherLoadFailure();
}
}
Stream<WeatherState> _mapWeatherRefreshRequestedToState(
WeatherRefreshRequested event,
) async* {
try {
final Weather weather = await weatherRepository.getWeather(event.city);
yield WeatherLoadSuccess(weather: weather);
} catch (_) {}
}
}
This is BlocConsumer:
// BlocBuilder1
BlocBuilder<WeatherBloc, WeatherState>(
builder: (context, state) {
if (state is WeatherLoadInProgress) {
return Center(child: CircularProgressIndicator());
}
if (state is WeatherLoadSuccess) {
final weather = state.weather;
return Center(child: Text("WeatherRequested "))
}
)
// BlocBuilder2
BlocBuilder<WeatherBloc, WeatherState>(
builder: (context, state) {
if (state is WeatherLoadInProgress) {
return Center(child: CircularProgressIndicator());
}
if (state is WeatherLoadSuccess) {
final weather = state.weather;
return Center(child: Text("WeatherRefreshRequested"))
}
)
The problem is that I want BlocBuilder1 only to work when the type of Event is WeatherRequested and BlocBuilder2 only works when the type of Event is WeatherRefreshRequested. One of my ideas is that each Event has its own State, and then judge the type of State in buildwhen.
Is there any good method?
if you want to build you widget to respond for certain states you should use
BlocConsumer and tell that bloc in buildWhen to tell it what state it should build/rebuild you widget on.
BlocConsumer<QuizBloc, QuizState>(
buildWhen: (previous, current) {
if (current is QuizPoints)
return true;
else
return false;
},
listener: (context, state) {},
builder: (context, state) {
if (state is QuizPoints)
return Container(
child: Center(
child: Countup(
begin: 0,
end: state.points,
duration: Duration(seconds: 2),
separator: ',',
),
),
);
else
return Container();
},
);
I have a simple service which is tracking the current user position :
class LocationService {
LatLng _lastLocation;
Location location = Location();
StreamController<LatLng> _locationController = StreamController<LatLng>();
Stream<LatLng> get locationStream => _locationController.stream;
LocationService() {
location.onLocationChanged().listen((locationData) {
LatLng location = LatLng(locationData.latitude, locationData.longitude);
if(_lastLocation == null || _lastLocation != location) {
_lastLocation = location;
_locationController.add(location);
}
});
}
}
Then, I'm using this service to create a Map (thanks to flutter_map) which is following the current user position :
class SelfUpdatingMap extends StatelessWidget {
final Icon currentPositionIcon;
final MapController _controller = MapController();
SelfUpdatingMap({
this.currentPositionIcon,
});
#override
Widget build(BuildContext context) => StreamBuilder<LatLng>(
stream: LocationService().locationStream,
builder: (context, asyncSnapshot) {
if (asyncSnapshot.hasError || asyncSnapshot.data == null) {
return Text('Loading...');
}
try {
_controller?.move(asyncSnapshot.data, 18);
} catch (ignored) {}
return _createMapWidget(context, asyncSnapshot.data);
},
);
Widget _createMapWidget(BuildContext context, LatLng location) => FlutterMap(
options: MapOptions(
center: location,
zoom: 18,
),
layers: [
TileLayerOptions(
urlTemplate: 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png', // https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png is good too.
subdomains: ['a', 'b', 'c'],
),
MarkerLayerOptions(
markers: [
Marker(
width: 40,
height: 40,
point: location,
builder: (contact) => currentPositionIcon,
),
]
),
],
mapController: _controller,
);
}
Then, I use the SelfUpdating widget in two places :
The page 1, ancestor of page 2.
And in the page 3, successor of page 2.
So here is the situation :
I launch my app, I'm on the page 1. I have my SelfUpdatingMap.
I call Navigator.pushNamed(context, '/page-2').
I call Navigator.pushNamed(context, '/page-3'). I have another SelfUpdatingMap.
I call two times Navigator.pop(context), I get the page 1 BUT the SelfUpdatingMap doesn't update itself anymore.
The builder is not even called anymore. So please, what is wrong with this code ?
Thank you !
When you push and after pop a page, the build metod doesn't restart.
I found the same problem with the FlutterBluetoothSerial.instance.onStateChanged() stream and the solution that I found is to add the stream to a local static final variable and use it instead of calling every time the original method (you can do that only if the stream is a broadcast one I think).
Solution example:
class ExampleClass {
static final Stream<LatLng> locationStream = LocationService().locationStream;
}
class SelfUpdatingMap extends StatelessWidget {
...
#override
Widget build(BuildContext context) => StreamBuilder<LatLng>(
stream: ExampleClass.locationStream,
builder: (context, asyncSnapshot) {
if (asyncSnapshot.hasError || asyncSnapshot.data == null) {
return Text('Loading...');
}
try {
_controller?.move(asyncSnapshot.data, 18);
} catch (ignored) {}
return _createMapWidget(context, asyncSnapshot.data);
},
);
...
}
class Page3Widget extends StatelessWidget {
...
#override
Widget build(BuildContext context) => StreamBuilder<LatLng>(
stream: ExampleClass.locationStream,
builder: (context, asyncSnapshot) {
//do something
},
);
...
}