BlocProvider inside initstate that receive value from blocbuilder - flutter

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;

Related

why posting data to firebase using flutter bloc is not emitting?

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)

Flutter GetX state management initial null value

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())) :

My future method is not working fine, while using flutter builder widget, Where did I go wrong?

Here is my stateful widget and url is a property pass it to the widget from parent widget. I don't know where did I go wrong?? I created a future builder widget that has getData() as a future. But the print statement inside was not executed ever. Why is that and it returns me always null value, and this results me a red container appearing on screen and not the table widget.
class TimeTable extends StatefulWidget {
final url;
const TimeTable({Key? key,required this.url}) : super(key: key);
#override
_TimeTableState createState() => _TimeTableState();
}
class _TimeTableState extends State<TimeTable> {
Future<List<Train>> getData() async{
final list = await TrainClient(url: widget.url).getName();
print("this line not executed");
return list;
}
#override
Widget build(BuildContext context) {
return Scaffold(body: FutureBuilder(
future: getData(),
builder: (context,projectSnap){
if(projectSnap.connectionState == ConnectionState.none ||
projectSnap.data == null) {
return Container(color: Colors.red,);
}
return buildDataTable(trains: projectSnap.data);
}));
}
}
getData is a future method and it returns a list, The list gets printed when I call that object Train Client. I had my print statement inside TrainClient class to check whether the list is created successfully.
Here is the code of TrainClient
class TrainClient {
final String url;
TrainClient({required this.url});
Future<List<Train>> getName() async {
final uri = Uri.parse(url);
final response = await get(uri);
if (response.statusCode == 200) {
print("ulla");
final data = json.decode(response.body);
final result = data["RESULTS"]["directTrains"]["trainsList"];
final list = result.map((json) => Train.fromJson(json));
print(list);
return list;
}else{
throw Exception();
}
}
}
The TrainClient class has no error since it printed the list successfully as shown below
(Instance of 'Train', Instance of 'Train', Instance of 'Train', ..., Instance of 'Train', Instance of 'Train')
You should always obtain future earlier (in initState/didChangeDependencies).
Each time your build is executed, new future is created. So it never finishes, if your widget rebuilds often.
late final _dataFuture = getData();
...
FutureBuilder(
future: _dataFuture,
builder: (context,projectSnap){
...
}
);

Flutter Bloc . I can't get hold of a value to pass in the state I yield in mapEventToState()`

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(...?));
}),

How to pop screen using Mobx in flutter

I have a Food object that contains properties like name, id, calories, etc. With a series of screens, the user populates the food object properties.
Once done, the user can press the submit button, that will call the addFood method in the store.
The problem is, after uploading the food to the server, i want to pop the screen or show error message in toast based on the response. I just don't know how to do this.
Following is my code (only the important bits):
FoodDetailStore.dart
class FoodDetailStore = _FoodDetailStore with _$FoodDetailStore;
abstract class _FoodDetailStore with Store {
Repository _repository;
Food _food;
#observable
String msg = '';
// ... Other Observables and actions
#action
addFood(bool toAdd) {
if (toAdd) {
_repository.addFood(food).then((docId) {
if (docId != null) {
// need to pop the screen
}
}).catchError((e) {
// show error to the user.
// I tried this, but it didn't work
msg = 'there was an error with message ${e.toString()}. please try again.';
});
}
// .. other helper methods.
}
FoodDetailScreen.dart (Ignore the bloc references, I am currently refactoring code to mobx)
class FoodDataScreen extends StatefulWidget {
final String foodId;
final Serving prevSelectedServing;
final bool fromNewRecipe;
FoodDataScreen({#required this.foodId, this.prevSelectedServing, this.fromNewRecipe});
#override
_FoodDataScreenState createState() => _FoodDataScreenState(
this.foodId,
this.prevSelectedServing,
this.fromNewRecipe,
);
}
class _FoodDataScreenState extends State<FoodDataScreen> {
final String foodId;
final Serving prevSelectedServing;
final bool fromNewRecipe;
FoodDataBloc _foodDataBloc;
_FoodDataScreenState(
this.foodId,
this.prevSelectedServing,
this.fromNewRecipe,
);
FoodDetailStore store;
#override
void initState() {
store = FoodDetailStore();
store.initReactions();
store.initializeFood(foodId);
super.initState();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
// I know this is silly, but this is what i tried. Didn't worked
Observer(
builder: (_) {
_showMsg(store.msg);
}
);
}
#override
Widget build(BuildContext context) {
return Container(
// ... UI
);
}
_popScreen() {
_showMsg('Food Added');
Majesty.router.pop(context);
}
_showMsg(String msg) {
Fluttertoast.showToast(msg: msg);
}
#override
void dispose() {
store.dispose();
super.dispose();
}
}
Constructing an Observer instance inside the didChangeDependencies() is indeed "silly" as you have rightly noted already :)
Observer is a widget and widget needs to be inserted into the widgets tree in order to do something useful. In our case non-widget Mobx reactions come to the rescue.
I will show how I did it in my code for the case of showing a Snackbar upon observable change so you will get an idea how to transform your code.
First of all, import import 'package:mobx/mobx.dart';.
Then in the didChangeDependencies() create a reaction which will use some of your observables. In my case these observables are _authStore.registrationError and _authStore.loggedIn :
final List<ReactionDisposer> _disposers = [];
#override
void dispose(){
_disposers.forEach((disposer) => disposer());
super.dispose();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_authStore = Provider.of<AuthStore>(context);
_disposers.add(
autorun(
(_) {
if (_authStore.registrationError != null)
_scaffoldKey.currentState.showSnackBar(
SnackBar(
content: Text(_authStore.registrationError),
backgroundColor: Colors.redAccent,
duration: Duration(seconds: 4),
),
);
},
),
);
_disposers.add(
reaction(
(_) => _authStore.loggedIn,
(_) => Navigator.of(context).pop(),
),
);
}
I use two types of Mobx reactions here: autorun and reaction. autorun triggers the first time immediately after you crate it and then every time the observable changes its value. reaction does not trigger the first time, only when the observable change.
Also pay attention to dispose the created reactions in the dispose() method to avoid resources leak.
Here is a code of my Mobx store class with used observables to complete the picture:
import 'package:mobx/mobx.dart';
import 'dart:convert';
part "auth_store.g.dart";
class AuthStore = AuthStoreBase with _$AuthStore;
abstract class AuthStoreBase with Store{
#observable
String token;
#observable
String registrationError;
#observable
String loginError;
#action
void setToken(String newValue){
token = newValue;
}
#action
void setRegistrationError(String newValue){
registrationError = newValue;
}
#action
void setLoginError(String newValue){
loginError = newValue;
}
#action
void resetLoginError(){
loginError = null;
}
#computed
bool get loggedIn => token != null && token.length > 0;
#action
Future<void> logOut() async{
setToken(null);
}
}