I'm creating an app with firebase as a database. After sending data to firebase, app screen should pop out for that I had bloclistener on the screen but after sending the data to firestore database, nothing is happening, flow is stopped after coming to loaded state in bloc file why? check my code so that you will know. I can see my data in firebase but it is not popping out because flow is not coming to listener.
state:
class SampletestInitial extends SampletestState {
#override
List<Object> get props => [];
}
class SampletestLoaded extends SampletestState {
SampletestLoaded();
#override
List<Object> get props => [];
}
class SampletestError extends SampletestState {
final error;
SampletestError({required this.error});
#override
List<Object> get props => [error];
}
bloc:
class SampletestBloc extends Bloc<SampletestEvent, SampletestState> {
SampletestBloc() : super(SampletestInitial()) {
on<SampletestPostData>((event, emit) async {
emit(SampletestInitial());
try {
await Repo().sampleTesting(event.des);
emit(SampletestLoaded());
} catch (e) {
emit(SampletestError(error: e.toString()));
print(e);
}
});
}
}
Repo: ---- Firebase post data
Future<void> sampleTesting(String des) async {
final docTicket = FirebaseFirestore.instance.collection('sample').doc();
final json = {'Same': des};
await docTicket.set(json);
}
TicketScreen:
//After clicking the button ---
BlocProvider<SampletestBloc>.value(
value: BlocProvider.of<SampletestBloc>(context, listen: false)
..add(SampletestPostData(description.text)),
child: BlocListener<SampletestBloc, SampletestState>(
listener: (context, state) {
if (state is SampletestLoaded) {
Navigator.pop(context);
print("Popped out");
}
},
),
);
im not sure but i think that you have the same hash of:
AllData? data;
try to remove AllData? data; and create new data variable so you can be sure that you has a new hash code every time you call createTicket method;
final AllData data = await repo.createTicket(AllData(
Check your AllData class properties.
BLoC will not show a new state if it not unique.
You need to check whether all fields of the AllData class are specified in the props field.
And check your BlocProvider. For what you set listen: false ?
BlocProvider.of<SampletestBloc>(context, listen: false)
Related
I am trying to fix an issue related to Flutter Bloc. I am editing someone else code to make it work with the latest flutter_bloc version but I am unable to do so. Can someone do a rewrite for my code so I can run it? I saw many answers but I am unable to understand how to fix my own code.
This is the complete code for all_categories_bloc.dart
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc({
this.apiRepository,
}) : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository apiRepository;
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
}
Code for all_categories_event.dart
abstract class AllCategoriesEvent extends Equatable {
AllCategoriesEvent();
}
class GetAllCategories extends AllCategoriesEvent {
#override
List<Object> get props => null;
}
Code for all_categories_state.dart
abstract class AllCategoriesState extends Equatable {
const AllCategoriesState();
}
class AllCategoriesInitial extends AllCategoriesState {
AllCategoriesInitial();
#override
List<Object> get props => [];
}
class AllCategoriesLoading extends AllCategoriesState {
const AllCategoriesLoading();
#override
List<Object> get props => null;
}
class AllCategoriesLoaded extends AllCategoriesState {
final CategoriesModel categoriesModel;
const AllCategoriesLoaded(this.categoriesModel);
#override
List<Object> get props => [categoriesModel];
}
class AllCategoriesError extends AllCategoriesState {
final String message;
const AllCategoriesError(this.message);
#override
List<Object> get props => [message];
}
It throws an error "Bad state: add(GetAllCategories) was called without a registered event handler.
Make sure to register a handler via on((event, emit) {...})"
I have this add(GetAllCategories) in my home. dart file but the solution is to edit this code which I am unable to do so. Can someone do a rewrite for the latest bloc? I would be thankful.
Let's get through the migration guide step by step:
package:bloc v5.0.0: initialState has been removed. For more information check out #1304.
You should simply remove the AllCategoriesState get initialState => AllCategoriesInitial(); portion from your BLoC.
package:bloc v7.2.0 Introduce new on<Event> API. For more information, read the full proposal.
As a part of this migration, the mapEventToState method was removed, each event is registered in the constructor separately with the on<Event> API.
First of all, register your events in the constructor:
AllCategoriesBloc() : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
Then, create the _onGetAllCategories method:
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await _apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
Notice, that instead of using generators and yielding the next state, you should use the Emitter<AllCategoriesState> emitter.
Here is the final result of the migrated AllCategoriesBloc:
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc() : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository _apiRepository = ApiRepository();
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await _apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
}
Bonus tip
Instead of creating an instance of ApiRepository inside the BLoC directly, you can use the constructor injection:
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc({
required this.apiRepository,
}) : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository apiRepository;
...
}
Now, when creating BLoC, pass the instance of the repository to the constructor, like AllCategoriesBloc(apiRepository: ApiRepository()). This way you will be able to properly unit test your BLoC by mocking dependencies (in this case, ApiRepository).
This is what I'm trying to achieve using flutter GetX package but not working properly.
I have a Firestore document, if the document is changed I want to call an api and keep the data up to date as observable.
The code below seems to work but initial screen shows null error then it shows the data.
I don't know how I can make sure both fetchFirestoreUser() and fetchApiData() (async methods) returns data before I move to the home screen.
GetX StateMixin seems to help with async data load problem but then I don't know how I can refresh the api data when the firestore document is changed.
I'm not sure if any other state management would be best for my scenario but I find GetX easy compared to other state management package.
I would very much appreciate if someone would tell me how I can solve this problem, many thanks in advance.
Auth Controller.
class AuthController extends SuperController {
static AuthController instance = Get.find();
late Rx<User?> _user;
FirebaseAuth auth = FirebaseAuth.instance;
var _firestoreUser = FirestoreUser().obs;
var _apiData = ProfileUser().obs;
#override
void onReady() async {
super.onReady();
_user = Rx<User?>(auth.currentUser);
_user.bindStream(auth.userChanges());
//get firestore document
fetchFirestoreUser();
//fetch data from api
fetchApiData();
ever(_user, _initialScreen);
//Refresh api data if firestore document has changed.
_firestoreUser.listen((val) {
fetchApiData();
});
}
Rx<FirestoreUser?> get firestoreUser => _firestoreUser;
_initialScreen(User? user) {
if (user == null) {
Get.offAll(() => Login());
} else {
Get.offAll(() => Home());
}
}
ProfileUser get apiData => _apiData.value;
void fetchFirestoreUser() async {
Stream<FirestoreUser> firestoreUser =
FirestoreDB().getFirestoreUser(_user.value!.uid);
_firestoreUser.bindStream(firestoreUser);
}
fetchApiData() async {
var result = await RemoteService.getProfile(_user.value!.uid);
if (result != null) {
_apiData.value = result;
}
}
#override
void onDetached() {}
#override
void onInactive() {}
#override
void onPaused() {}
#override
void onResumed() {
fetchApiData();
}
}
Home screen
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Obx(() =>
Text("username: " + AuthController.instance.apiData.username!))),
),
);
}
}
To be honest, I never used GetX so I'm not too familiar with that syntax.
But I can see from your code that you're setting some mutable state when you call this method:
fetchApiData() async {
var result = await RemoteService.getProfile(_user.value!.uid);
if (result != null) {
_apiData.value = result;
}
}
Instead, a more robust solution would be to make everything reactive and immutable. You could do this by combining providers if you use Riverpod:
final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) {
final authService = ref.watch(authRepositoryProvider);
return authService.authStateChanges();
});
final apiDataProvider = FutureProvider.autoDispose<APIData?>((ref) {
final userValue = ref.watch(authStateChangesProvider);
final user = userValue.value;
if (user != null) {
// note: this should also be turned into a provider, rather than using a static method
return RemoteService.getProfile(user.uid);
} else {
// decide if it makes sense to return null or throw and exception when the user is not signed in
return Future.value(null);
}
});
Then, you can just use a ConsumerWidget to watch the data:
#override
Widget build(BuildContext context, WidgetRef ref) {
// this will cause the widget to rebuild whenever the auth state changes
final apiData = ref.watch(apiDataProvider);
return apiData.when(
data: (data) => /* some widget */,
loading: () => /* some loading widget */,
error: (e, st) => /* some error widget */,
);
}
Note: Riverpod has a bit of a learning curve (worth it imho) so you'll have to learn it how to use it first, before you can understand how this code works.
Actually the reason behind this that you put your controller in the same page that you are calling so in the starting stage of your page Get.put() calls your controller and because you are fetching data from the API it takes a few seconds/milliseconds to get the data and for that time your Obx() renders the error. To prevent this you can apply some conditional logic to your code like below :
Obx(() => AuthController.instance.apiData != null ? Text("username: " + AuthController.instance.apiData.username!) : CircularProgressIndicator())) :
I am to listening firebase AuthStateChanges stream and provide the stream with streamProvider to change the view based on the stream value. And I did this:
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Stream get currentUser => _auth.authStateChanges();
}
final userStream = StreamProvider.autoDispose((ref) => AuthService().currentUser);
class AuthenticationWrapper extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final user = watch(userStream);
print('AutenticationWrapper build method got called');
return user.when(
data: (data) {
if (data?.uid == null) {
print('I am currently Logged out ๐');
return LogInPage();
} else {
print('I am logged in user๐');
return HomePage();
}
},
loading: () => CircularProgressIndicator(),
error: (e, s) => Text('Oops'),
);
}
}
I was expecting to have LogIn page to be rendered when the AsyncValue of the streamProvider gets changed. The above code didn't work as expected; in fact, it prints the message but it doesn't return the Widget it is supposed to return. However, when I hot restart the app it will render the correct Widget based on the stream value.
Why doesn't the build method re-render when this: final user = watch(userStream); receives an update?
I think you should probably watch(userStream.stream) to be notified when the stream itself updates. Haven't played much with StreamProvider though, so I could be wrong.
i curious how to call BlocProvider inside inistate that retrieve value from BlocBuilder.
usually inside widget i call Blocbuilder to retrieve a string and then the string value i can pass to Bloc provider so will run fetch data.
i try in inistate like below :
void initState(){
super.initState();
BlocBuilder<idoutletbloc,String>(
builder: (context,idoutlet)=>BlocProvider.of<tablelistbloc>(context).add(idoutlet);
);}
but it said "The return is void isn't Widget", as defined by anonymous closure.
how i can retrieve a value from idoutletbloc and then i can add BlocProvider.of(context).add(idoutlet) ??
i can't find it anywhere
here my bloc code
this is my bloc string value
class idoutletbloc extends Bloc<String, String>{
#override
String get initialState => '';
#override
Stream<String> mapEventToState(String idoutlet) async* {
yield idoutlet.toString();
}
}
my bloc that fetch data that need to receive a value
class viewtableactivebloc extends Bloc<String, List<ViewTableActives>>{
#override
List<ViewTableActives> get initialState => [];
#override
Stream<List<ViewTableActives>> mapEventToState(String event) async*{
List<ViewTableActives> viewtableactive =[];
try{
final response = await http.post(BaseUrl.ViewTableActive,
body: {
"ID_Outlet": event,
});
final data = jsonDecode(response.body);
if (data.length != 0) {
print("----------START Print View Table Active----------");
data.forEach((api) {
viewtableactive.add(
ViewTableActives(
api['ID_Transactions'],
api['ID_Customer'],
)
);
print("Print View Table Actibe : "
"ID_Transactions : "+api['ID_Transactions']+", "
"ID_Customer : "+api['ID_Customer']+", "
);
});
print("----------END Print View Table Active----------");
print("viewtableactivebloc : sukses");
} else {
viewtableactive =[];
print('data kosong');
}
}
catch(e){
print("Error ViewTableActive :");
print(e);
}
yield viewtableactive;
}}
You can access the current state of the bloc like:
BlocProvider.of(context).state;
or via the shorthand
context.bloc().state;
I'm just starting with Flutter and I'm still uncertain about the logic to structure event/state/BLoc/Repositoy classes in order to use this pattern correctly. I'm stuck at getting a location value out of the bloc back to the UI when yielding a state that has the value as the input.
Starting from the repository I have getLocation() method that get coordinates from Geolocator locationManager :
Future<LatLng> getLocation() async {
try {
locationManager
.getCurrentPosition(
desiredAccuracy: LocationAccuracy.bestForNavigation)
.timeout(Duration(seconds: 5));
} catch (error) {
print(
'getLocation(): error getting current location: ${error.toString()}');
}
}
then I have the GetLocation event
class GetLocation extends MapEvent {
final LatLng location;
const GetLocation(this.location);
#override
List<Object> get props => [location];
#override
String toString() => 'GetLocation { current location: $location}';
}
that gets sent to bloc at screen loading to present the map, and from a button to center the map on user position.
BlocProvider<MapBloc>(create: (context) {
return MapBloc(mapRepository: MapRepository())..add(GetLocation());
}),
then the MapLoaded state that holds the location:
class MapLoaded extends MapState {
final LatLng location;
const MapLoaded(this.location);
#override
List<Object> get props => [location];
#override
String toString() => 'MapLoaded {location: $location}';
}
this is the Widget that gets built on that state:
BlocBuilder<MapBloc, MapState>(
bloc: MapBloc(mapRepository: _mapRepository),
builder: (BuildContext context, MapState state) {
final LatLng location = (state as MapLoaded).location;
return Container
and finally the bloc where I can't find a way to pass the location as the input of MapState()in response to an event :
class MapBloc extends Bloc<MapEvent, MapState> {
final MapRepository _mapRepository;
StreamSubscription _locationStreamSubscription;
MapBloc(
{#required MapRepository mapRepository,
#required StreamSubscription streamSubscription})
: assert(mapRepository != null || streamSubscription != null),
_mapRepository = mapRepository,
_locationStreamSubscription = streamSubscription;
MapState get initialState => MapLoading();
#override
Stream<MapState> mapEventToState(MapEvent event) async* {
if (event is GetLocation) {
yield* _mapGetLocationToState();
}
if (event is GetTracking) {
yield* _mapGetTrackingToState();
}
if (event is LocationUpdated) {
// CANT GET LOCATION TO PASS IN MapLoaded()
yield MapLoaded();
}
}
Stream<MapState> _mapGetLocationToState() async* {
_mapRepository.getLocation();
(location) => add(LocationUpdated(location));
// CANT GET LOCATION TO PASS IN MapLoaded()
yield MapLoaded(l);
}
Stream<MapState> _mapGetTrackingToState() async* {
_mapRepository.setTracking();
}
}
Inside Stream<MapState> _mapGetLocationToState() I tried to send a add(LocationUpdated(location)) event and in Stream<MapState> mapEventToState to yield MapLoaded() but I can't find any way to pass in the location. I also tried to yield it directly in Stream<MapState> _mapGetLocationToState() but with the same result.
Can you spot what I'm doing wrong? Switching to reactive programming is not being that easy but I'm getting there.. This is my first attempt to this pattern and I haven't wrapped my head around all concepts completely so I surely thought some classes wrongly.
Many thanks for your time and help and sorry for the long question.
Cheers.
Your _mapGetLocationToState() doesn't have event as param.
#override
Stream<MapState> mapEventToState(MapEvent event) async* {
if (event is GetLocation) {
yield* _mapGetLocationToState(event);
}
if (event is GetTracking) {
yield* _mapGetTrackingToState();
}
if (event is LocationUpdated) {
// CANT GET LOCATION TO PASS IN MapLoaded()
yield MapLoaded();
}
}
Stream<MapState> _mapGetLocationToState(GetLocation event) async* {
// now you have event.location
_mapRepository.getLocation();
(location) => add(LocationUpdated(location));
// CANT GET LOCATION TO PASS IN MapLoaded(event.location)
yield MapLoaded(event.location);
}
Stream<MapState> _mapGetTrackingToState() async* {
_mapRepository.setTracking();
}
}
EDIT: BlocProvider task is to make an instance of the Bloc class you want to have. In this case MapBloc. As you can see, your MapBloc class has 2 dependencies, MapRepository and StreamSubscription. So when BlocProvider wants to make an instance of it, you need to provide those things it needs through the constructor. The same thing as GetLocation, you need to provide LatLng because it depends on it.
BlocProvider<MapBloc>(create: (context) {
return MapBloc(
mapRepository: MapRepository(),
streamSubscription: ...?
)..add(GetLocation(...?));
}),