Data not rebuild (restart) with rxdart - flutter

I do floating alert which is controlled with stream. The logic of that alert is, when tap the OK button on it to turn true to stream and then close.
I will show my codes, and please fix me what my logic wrong.
Widget dataState(
AsyncSnapshot pinValidateSnapshot, ChangePinBloc changePinBloc) {
if (pinValidateSnapshot.data == true) {
WidgetsBinding.instance.addPostFrameCallback(
(_) => Navigator.pushNamed(context, newConfirmPinRoute));
}
else if(pinValidateSnapshot.data == false){
return StreamBuilder(
stream: changePinBloc.alertStream,
builder: (context, AsyncSnapshot<bool> alertSnapshot) {
// if (alertSnapshot.hasError) {
// changePinBloc.onCloseAlert(false);
// }
return Positioned.fill(
child: Visibility(
visible: alertSnapshot.data != true,
child: AlertContainerView(
message: 'Same',
onOKTap: () {
changePinBloc.onCloseAlert(true);
},
),
),
);
},
);
}
return Center(child: Text('WTF'),);
}
Here is codes in bloc class
final alertController = PublishSubject<bool>();
Stream<bool>get alertStream => alertController.stream;
void onCloseAlert(bool flag) {
alertController.sink.add(flag);
}
In here although pinValidateSnapshot change state, the alertSnapshot don't change start again from initial state, it always left previous state and alert not show again. How to do with that?

Related

How to conditionally show Get.bottomSheet when starting app

I am trying to popup Get.bottomSheet when starting app.
I did it like bottom code.
#override
void initState() {
_popUpBottomBanner();
}
void _popUpBottomBanner() {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
Get.bottomSheet(
...
);
},
);
}
But I want to judge show or hide bottom sheet by API result.
So I changed Code like below.
void _popUpBottomBanner() {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
FutureBuilder<CustomListVO>(
future: Api().getBanners(...),
builder: (BuildContext _, AsyncSnapshot<CustomListVO> snapshot) {
if (snapshot.hasError) return;
if (!snapshot.hasData) return;
if (snapshot.data?.list?.isNotEmpty == true) {
Get.bottomSheet(
...
);
}
return;
},
);
},
);
}
Despite confirming API result arriving properly,
bottomSheet isn't showing.
What is the problem with this code?
Thanks for reading :D
====== resolved like below ========
#override
Widget build(BuildContext context) {
return FutureBuilder<CustomListVO>(
future: Api().getBanners(...),
builder: (BuildContext _, AsyncSnapshot<CustomListVO> snapshot) {
if (snapshot.data?.list?.isNotEmpty == true) {
Get.bottomSheet(
...
);
}
return HomePage();
}
);
}
you need to show bottom sheet that is not required to use Futurebuilder so you can use it like this :
var result = await Api().getBanners(...);
if (result.data?.list?.isNotEmpty == true) {
Get.bottomSheet(
...
);
}

Futurebuilder is not updating data from firestore

So i am having issue with futurebuilder i want my app to update when a bool is set true but it wasn't working at all so i added a line to to see if the value of bool is changing or not and released it's not changing.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:databse_web_test/database_services/getsocials.dart';
import 'package:flutter/material.dart';
import 'database_services/request.dart';
class RequestWidget extends StatefulWidget {
RequestWidget({Key? key}) : super(key: key);
#override
State<RequestWidget> createState() => _RequestWidgetState();
}
class _RequestWidgetState extends State<RequestWidget> {
String Doc = "EobkN9fONF4IxmpErB1n";
CollectionReference request = FirebaseFirestore.instance
.collection('socails')
.doc("daaJgE8Pz5UQIlNh47UsmwWcqNi1")
.collection("requests");
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: request.doc("EobkN9fONF4IxmpErB1n").get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return const Text("Document does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data =
snapshot.data!.data() as Map<String, dynamic>;
bool isRequested = data["isRequested"];
bool isApproved = data["isApproved"];
if (data["isRequested"] == true && data['isApproved'] == true) {
return GetSocialData();
}
// if (data['isApproved'] == false && data['isRequested'] == true) {
// return Column(
// children: [
// data['isApproved'] == false
// ? const CircularProgressIndicator()
// : GetSocialData()
// ],
// );
// }
if (data['isApproved'] == false && data["isRequested"] == false) {
return Center(
child: ElevatedButton(
onPressed: () {
SendRequest().updateUserData(
isApproved: false, isRequested: true);
setState(() {});
},
child: const Text("data send")));
} else {
return Column(children: [
CircularProgressIndicator(),
Text(snapshot.data!.data().toString())
]);
}
} else {
return const Text("Loading database");
}
});
// if (isRequested == true && isApproved == false) {
// return Center(
// child: ElevatedButton(
// onPressed: () {
// SendRequest()
// .updateUserData(isApproved: false, isRequested: true);
// },
// child: const Text("data send")));
// } else {
// return GetSocialData();
// }
}
}
i really don't know whats wrong since im new to flutter i dont know that much. if i were to use text widget to know if the value is changing i get to know that value isn't changing. this web app is connect to another android app and value of that bool is gonna be updated by that app
A flutter builder it is a one time read, because, if you want to use a realtime read, use a streambuilder, check that in documentation : Flutter Cloud Firestore.
FutureBuilder is used for one time response, like taking an image from Camera, getting data once from native platform (like fetching device battery), getting file reference, making an http request etc.
On the other hand, StreamBuilder is used for fetching some data more than once, like listening for location update, playing a music, stopwatch, etc.
In your case you should use StreamBuilder

the code after navigator.pushReplacement still executes? (Flutter)

This works. But I don't understand the exact flow.
class _LoadingPageState extends State<LoadingPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: this.checkGpsYLocation(context),
builder: (BuildContext context, AsyncSnapshot snapshot) {
print(snapshot.data);
if ( snapshot.hasData ) {
return Center(child: Text( snapshot.data ) );
} else {
return Center(child: CircularProgressIndicator(strokeWidth: 2 ) );
}
},
),
);
}
Future checkGpsYLocation( BuildContext context ) async {
// PermisoGPS
final permisoGPS = await Permission.location.isGranted;
// GPS está activo
final gpsActivo = await Geolocator().isLocationServiceEnabled();
if ( permisoGPS && gpsActivo ) {
Navigator.pushReplacement(context, navegarMapaFadeIn(context, MapaPage() ));
return '';
} else if ( !permisoGPS ) {
Navigator.pushReplacement(context, navegarMapaFadeIn(context, AccesoGpsPage() ));
return 'Es necesario el permiso de GPS';
} else {
return 'Active el GPS';
}
}
}
When Navigator.pushReplacement executes. What happens with rest of code after, like the return 'Es necesario el permiso GPS'. Does it still execute? Or is it ignored by disposing widget (Assuming pushReplacement disposes it)?
Flutter is a normal code after all, statments are executed in sequence one after another, unless of course you have async, loop, recursion etc.
And thats applys also to all Navigator methods. Yes the UI changes but the code flow will continue normaly till it hit a return (or anything else that could end the flow like break, the end of void method....).
Hope that makes clear

How to wait multiple bloc events in same bloc

I have one bloc with multiple events. Here I load categories and locations and wait using BlocListener. But my condition for show circular progress indicator work incorrectly and after load categories and locations also shows. How I can use bloc correctly in this case?
Code
apiDataBloc.add(LoadCategoriesEvent());
apiDataBloc.add(LoadLocationsEvent());
------------------------
return BlocListener<ApiDataBloc, ApiDataState>(
listener: (context, state) {
if (state is CategoriesLoaded) {
categories = state.categories;
print("Categories loaded");
print(categories.length);
}
},
child: BlocListener<ApiDataBloc, ApiDataState>(
listener: (context, s) {
if (s is LocationsLoaded) {
locations = s.locations;
print("Locations loaded");
print(locations.length);
}
},
child: locations != null &&
categories != null &&
categories.length > 0 &&
locations.length > 0
? Container(child: Center(child: Text('Categories and locations loaded!')))
: Container(child: Center(child: CircularProgressIndicator())),
),
);
I tried also like this but doesn't work.
return BlocProvider<ApiDataBloc>(
create: (context) => apiDataBloc,
child: BlocBuilder<ApiDataBloc, ApiDataState>(
builder: (context, state) {
if (state is LocationsLoaded) {
print("Locations loaded");
locations = state.locations;
print(locations.length);
return BlocBuilder<ApiDataBloc, ApiDataState>(
builder: (context, s) {
if (s is CategoriesLoaded) {
print("Categories loaded");
categories = s.categories;
print(categories.length);
return Container(
child: Center(
child: Text('Categories and locations loaded!')));
}
return Container(
child: Center(child: CircularProgressIndicator()));
},
);
}
return Container(child: Center(child: CircularProgressIndicator()));
},
),
);
You should create one state DataLoaded with 2 fields categories and locations
Something like that:
class DataLoaded extends ApiDataState {
const DataLoaded(
this.categories,
this.locations,
);
final List<Type> categories;
final List<Type> locations;
#override
String toString() => 'DataLoaded';
}
Then you need to fetch data from API in the ApiDataBloc class:
class ApiDataBloc extends Bloc<YourEventType, ApiDataState> {
ApiDataBloc() : super(YourInitialState());
#override
Stream<ApiDataState> mapEventToState(YourEventType event) async* {
if (event is YourFetchApiEvent) {
yield YourLoadingState();
final categories = await _fetchCategories();
final locations = await _fetchLocations();
yield DataLoaded(categories,locations);
}
}
}
and the final step is BlocBuilder in your widget:
return BlocProvider<ApiDataBloc>(
create: (context) => apiDataBloc,
child: BlocBuilder<ApiDataBloc, ApiDataState>(
builder: (context, state) {
if (state is YouLoadingState) {
return Center(child: CircularProgressIndicator());
}
if (state is DataLoaded) {
print(state.locations);
print(state.categories);
return Center(
child: Text('Categories and locations loaded!'),
);
}
},
),
);
I would place the logic into the bloc. If I understand correctly, you get an event triggered as soon as the data is loaded. Then you could create 2 variables in the bloc bool categoriesLoaded, locationsLoaded which you set true upon the event. In mapEventToState you could forward from each of those event mappers to a common event mapper that checks if both variables are true and sends the proper state then. An inProgress state could display which of the data streams has already been loaded.
I know what you meant.
Example Case:
#some_bloc.dart (not in event or state file)
on<someEventNo1>((......) =>
emit(LoadingState());
emit(EmitResultAPI());
on<someEventNo2>((......) =>
emit(LoadingState());
emit(someState());
#main.dart
someMethod() {
BlocProvider.of<SomeBloc>(context).add(someEventNo1());
BlocProvider.of<SomeBloc>(context).add(someEventNo2());
}
If you do your code like that, bloc builder will not catch state change when someEventNo1 emits EmitResultAPI, because you are sending 2 consecutive BlocProvider.of<>().
Solution:
BlocProvider.of<SomeBloc>(context).add(someEventNo1());
Future.delayed(Duration(miliseconds: 100)).then((valueFuture) => BlocProvider.of<SomeBloc>(context).add(someEventNo2()));

How to make the connection to waiting state by using StreamBuilder in flutter

My requirement is to make that StreamBuilder connection state to waiting.
I'm using publish subject, whenever I want to load data in stream builder I'm just adding data to the sink by calling postStudentsToAssign() method, here this method making an API call which takes some time, in that time I to want make that streamBuilder connection state to waiting
Stream Builder:
StreamBuilder(
stream: studentsBloc.studentsToAssign,
// initialData: [],
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
// While waiting for the data to load, show a loading spinner.
return getLoader();
default:
if (snapshot.hasError)
return Center(child: Text('Error: ${snapshot.error}'));
else
return _getDrawer(snapshot.data);
}
}),
Initializing Observable:
final _assignStudentSetter = PublishSubject<dynamic>();
Observable<List<AssignMilestoneModel>> get studentsToAssign =>
_studentsToAssignFetcher.stream;
Method that add's data to Stream:
postStudentsToAssign(int studyingClass, String milestoneId, String subject,
List studentList) async {
var response = await provider.postAssignedStudents(
studyingClass, milestoneId, subject, studentList);
_assignStudentSetter.sink.add(response);
}
You can send null to the stream, so the snapshot.connectionState changes to active. I don't know why and whether it's official solution, but it works (at least now). I found this accidentally.
I would like the Flutter team to explain how to set snapshot's connectionState. It's not clear from StreamBuilder documentation. It seems you should replace the stream with a new one to have snapshot in waiting state. But it's agains the logic you want to implement.
I checked StreamBuilder source to find out that the AsyncSnapshot.connectionState starts as waiting (after stream is connected), after receiving data changes to active. snapshot.hasData returns true if snapshot.data != null. That's how following code works.
class SearchScreen extends StatelessWidget {
final StreamController<SearchResult> _searchStreamController = StreamController<SearchResult>();
final SearchService _service = SearchService();
void _doSearch(String text) async {
if (text?.isNotEmpty ?? false) {
_searchStreamController.add(null);
_searchService.search(text)
.then((SearchResult result) => _searchStreamController.add(result))
.catchError((e) => _searchStreamController.addError(e));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: <Widget>[
SearchBar(
onChanged: (text) => _doSearch(text),
),
StreamBuilder<SearchResult>(
stream: _searchStreamController.stream,
builder: (BuildContext context, AsyncSnapshot<SearchResult> snapshot) {
Widget widget;
if (snapshot.hasData) {
widget = Expanded(
// show search result
);
}
else if (snapshot.hasError) {
widget = Expanded(
// show error
);
}
else if(snapshot.connectionState == ConnectionState.active){
widget = Expanded(
// show loading
);
}
else {
// empty
widget = Container();
}
return widget;
},
),
]),
);
}
}