How to conditionally show Get.bottomSheet when starting app - flutter

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

Related

Data not rebuild (restart) with rxdart

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?

StreamBuilder / ChangeNotifierProvider- setState() or markNeedsBuild() called during build

Streambuilder, ChangeNotifier and Consumer cannot figure out how to use correctly. Flutter
I've tried and tried and tried, I've searched a lot but I cannot figure this out:
I'm using a Streambuilder this should update a ChangeNotifier that should trigger rebuild in my Consumer widget. Supposedly...
but even if I call the provider with the (listen: false) option I've got this error
The following assertion was thrown while dispatching notifications for
HealthCheckDataNotifier: setState() or markNeedsBuild() called during
build. the widget which was currently being built when the offending call was made was:
StreamBuilder<List>
Important: I cannot create the stream sooner because I need to collect other informations before reading firebase, see (userMember: userMember)
Widget build(BuildContext context) {
return MultiProvider(
providers: [
/// I have other provider...
ChangeNotifierProvider<HealthCheckDataNotifier>(create: (context) => HealthCheckDataNotifier())
],
child: MaterialApp(...
then my Change notifier look like this
class HealthCheckDataNotifier extends ChangeNotifier {
HealthCheckData healthCheckData = HealthCheckData(
nonCrewMember: false,
dateTime: DateTime.now(),
cleared: false,
);
void upDate(HealthCheckData _healthCheckData) {
healthCheckData = _healthCheckData;
notifyListeners();
}
}
then the Streambuilder
return StreamBuilder<List<HealthCheckData>>(
stream: HeathCheckService(userMember: userMember).healthCheckData,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData) {
if (snapshot.data!.isNotEmpty) {
healthCheckData = snapshot.data?.first;
}
if (healthCheckData != null) {
timeDifference = healthCheckData!.dateTime.difference(DateTime.now()).inHours;
_cleared = healthCheckData!.cleared;
if (timeDifference < -12) {
healthCheckData!.cleared = false;
_cleared = false;
}
///The problem is here but don't know where to put this or how should be done
Provider.of<HealthCheckDataNotifier>(context, listen: false).upDate(healthCheckData!);
}
}
return Builder(builder: (context) {
return Provider<HealthCheckData?>.value(
value: healthCheckData,
builder: (BuildContext context, _) {
return const HealthButton();
},
);
});
} else {
return const Text('checking health'); //Scaffold(body: Loading(message: 'checking...'));
}
});
and finally the Consumer (note: the consumer is on another Route)
return Consumer<HealthCheckDataNotifier>(
builder: (context, hN, _) {
if (hN.healthCheckData.cleared) {
_cleared = true;
return Container(
color: _cleared ? Colors.green : Colors.amber[900],
Hope is enough clear,
Thank you so very much for your time!
it is not possible to setState(or anything that trigger rerender) in the builder callback
just like you don't setState in React render
const A =()=>{
const [state, setState] = useState([])
return (
<div>
{setState([])}
<p>will not work</p>
</div>
)
}
it will not work for obvious reason, render --> setState --> render --> setState --> (infinite loop)
so the solution is similar to how we do it in React, move them to useEffect
(example using firebase onAuthChange)
class _MyAppState extends Stateful<MyApp> {
StreamSubscription<User?>? _userStream;
var _waiting = true;
User? _user;
#override
void initState() {
super.initState();
_userStream = FirebaseAuth.instance.authStateChanges().listen((user) async {
setState(() {
_waiting = false;
_user = user;
});
}, onError: (error) {
setState(() {
_waiting = false;
});
});
}
#override
void dispose() {
super.dispose();
_userStream?.cancel();
}
#override
Widget build(context) {
return Container()
}
}

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

Rebuild a FutureBuilder

I am trying to rebuild a FutureBuilder, in my case it is for the purpose of changing the camera that is shown to the user. For that I have to run the Future, the FutureBuilder uses again.
The Future currently looks like this:
body: FutureBuilder(
future: _initializeCameraControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return CameraPreview(_cameraController);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
The Future it is running is this one:
Future<void> _initializeCameraControllerFuture;
#override
void initState() {
super.initState();
if (camerafrontback != true) {
_cameraController =
CameraController(widget.camera[0], ResolutionPreset.veryHigh);
}else{
_cameraController =
CameraController(widget.camera[1], ResolutionPreset.veryHigh);
}
_initializeCameraControllerFuture = _cameraController.initialize();
}
I have a button that should trigger this rebuild of the FutureBuilder, it is also changing the 'camerafrontback' Boolean, so that a different camera is used. It is shown below:
IconButton(
onPressed: () async {
if (camerafrontback == true){
setState(() {
camerafrontback = false;
});
}else{
setState(() {
camerafrontback = true;
});
};
},
At the end, before the last bracket, there must be a statement added that triggers the rebuilds of whole FutureBuilder, but I couldn't find one that suits to my code.
Thanks in advance for your answers!
You need to reinitialize_initializeCameraControllerFuture.
You can do it something like this
#override
void initState() {
super.initState();
_init();
}
...
_init() {
if (camerafrontback) {
_cameraController =
CameraController(widget.camera[1], ResolutionPreset.veryHigh);
} else {
_cameraController =
CameraController(widget.camera[0], ResolutionPreset.veryHigh);
}
_initializeCameraControllerFuture = _cameraController.initialize();
}
...
IconButton(
onPressed: () async {
if (camerafrontback) {
camerafrontback = false;
setState(_init());
} else {
camerafrontback = true;
setState(_init);
}
},
),
to full rebuild your screen you can just replace the screen with the same screen
Navigator.pushReplacement(context,
MaterialPageRoute(
builder: (BuildContext context) =>
*SamePage*()
)
);

Rxdart combinelaststream function does not work

I am going to combine two stream. But it does not work. What is my mistake ?
My build function is ;
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: Observable.combineLatest2(
getAllDBAccountsBloc.getAllDBAccountsStream,
deleteDBAccountBloc.deleteDBAccountStream,
(accountList, deleteAccountResultModel) {
print("my account list : ${accountList == null}");
return AccountsCombinerResult(
deleteAccountResultBlocModel: deleteAccountResultModel,
accountsList: accountList,
);
},
),
builder: (context, snapshot) {
print("hasData : ${snapshot.hasData}");
if (snapshot.hasData) accountsCombinerResult = snapshot.data;
if (snapshot.hasError) return Text(snapshot.error.toString());
return _buildWidget;
},
);
}
Get All DB Accounts Stream Bloc is
class GetAllDBAccountsBloc {
final _getAllDBAccountsFetcher = PublishSubject<List<AccountDatabaseModel>>();
Observable<List<AccountDatabaseModel>> get getAllDBAccountsStream => _getAllDBAccountsFetcher.stream;
getAllDBAccounts() async {
print("accounts getting");
_getAllDBAccountsFetcher.sink.add(null);
await new Future.delayed(const Duration(seconds: 1));
_getAllDBAccountsFetcher.sink.add(await Repository.getAllDBAccounts());
print("accounts get");
}
dispose() {
_getAllDBAccountsFetcher.close();
}
}
final getAllDBAccountsBloc = GetAllDBAccountsBloc();
Delete DB Account Bloc is
class DeleteDBAccountBloc {
final _deleteDBAccountFetcher = PublishSubject<DeleteAccountResultBlocModel>();
Observable<DeleteAccountResultBlocModel> get deleteDBAccountStream => _deleteDBAccountFetcher.stream;
deleteDBAccount(DeleteAccountRequestBlocModel requestModel) async {
_deleteDBAccountFetcher.sink.add(DeleteAccountResultBlocModel());
await new Future.delayed(const Duration(seconds: 1));
_deleteDBAccountFetcher.sink.add(await Repository.deleteDBAccount(requestModel));
}
dispose() {
_deleteDBAccountFetcher.close();
}
}
final deleteDBAccountBloc = DeleteDBAccountBloc();
Combiner result class is
class AccountsCombinerResult {
final DeleteAccountResultBlocModel deleteAccountResultBlocModel;
final List<AccountDatabaseModel> accountsList;
AccountsCombinerResult({
#required this.accountsList,
#required this.deleteAccountResultBlocModel,
});
}
its mine Run log on android studio..
I/flutter (28323): accounts getting
I/flutter (28323): hasData : false
I/flutter (28323): hasData : false
I/flutter (28323): accounts get
The stream work but i did not get AccountsCombiner Result data.
This build method work but i don't want use it...
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: getAllDBAccountsBloc.getAllDBAccountsStream,
builder: (context, getDbAccountsSnapshot) {
return StreamBuilder(
stream: deleteDBAccountBloc.deleteDBAccountStream,
builder: (context, deleteDbAccountStreamSnapshot) {
if (deleteDbAccountStreamSnapshot.hasData && getDbAccountsSnapshot.hasData) {
print("qweqweq");
accountsCombinerResult = AccountsCombinerResult(
accountsList: getDbAccountsSnapshot.data,
deleteAccountResultBlocModel: deleteDbAccountStreamSnapshot.data,
);
}
if (getDbAccountsSnapshot.hasError) return Text(getDbAccountsSnapshot.error.toString());
if (deleteDbAccountStreamSnapshot.hasError) return Text(deleteDbAccountStreamSnapshot.error.toString());
return _buildWidget;
},
);
},
);
}
You are building a new stream every time the build method is called. You need to keep the stream reference in the state.
StreamController<AccountsCombinerResult> _streamController = StreamController<AccountsCombinerResult>();
#override
void initState() {
super.initState();
_streamController.addStream(Observable.combineLatest2(
getAllDBAccountsBloc.getAllDBAccountsStream,
deleteDBAccountBloc.deleteDBAccountStream,
(accountList, deleteAccountResultModel) {
print("my account list : ${accountList == null}");
return AccountsCombinerResult(
deleteAccountResultBlocModel: deleteAccountResultModel,
accountsList: accountList,
);
},
));
}
#override
void dispose() {
super.dispose();
_streamController.close();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _streamController.stream,
builder: (context, snapshot) {
print("hasData : ${snapshot.hasData}");
if (snapshot.hasData) accountsCombinerResult = snapshot.data;
if (snapshot.hasError) return Text(snapshot.error.toString());
return _buildWidget;
},
);
}
To make this easier you could use the StreamProvider from the provider package.
https://pub.dev/packages/provider
https://pub.dev/documentation/provider/latest/provider/StreamProvider-class.html
It only build the stream once.
#override
Widget build(BuildContext context) {
return StreamProvider<AccountsCombinerResult>(
initialData: null, // not sure if this works, you can try []
create: () => Observable.combineLatest2(
getAllDBAccountsBloc.getAllDBAccountsStream,
deleteDBAccountBloc.deleteDBAccountStream,
(accountList, deleteAccountResultModel) {
print("my account list : ${accountList == null}");
return AccountsCombinerResult(
deleteAccountResultBlocModel: deleteAccountResultModel,
accountsList: accountList,
);
},
),
catchError: (context, error) => AccountsCombinerResult(
deleteAccountResultBlocModel: null,
accountsList: null,
error: error,
),
child: Builder(
builder: (context) {
final data = Provider.of<AccountsCombinerResult>(context);
// maybe null check
if (data.error != null) return Text(data.error.toString());
accountsCombinerResult =data;
return _buildWidget;
},
),
);
}
class AccountsCombinerResult {
final DeleteAccountResultBlocModel deleteAccountResultBlocModel;
final List<AccountDatabaseModel> accountsList;
final dynamic error;
AccountsCombinerResult({
#required this.accountsList,
#required this.deleteAccountResultBlocModel,
this.error,
});
}
The code is not tested so there may be typos or stuff that I missed, but you should get the general idea.