Flutter wait for signal from firestore bool - flutter

I have a question about Flutter and Firestore.
I want to wait until another app set the bool from Firestore "roomStart" to true, to open a view. If "roomStart" is false, it should wait until it is set to true and then start the if statement again.
class QuizPage extends StatefulWidget {
final Room room;
QuizPage(this.questions, this.room);
#override
_QuizPageState createState() => _QuizPageState(room);
}
class _QuizPageState extends State<QuizPage> {
final Room room;
_QuizPageState(this.room);
#override
Widget build(BuildContext context) {
if(room.roomStart) {
return MaterialApp(
home: Scaffold(
//code
);
} else {
// code: wait for boolean is set on true
);
}
}
}
enter image description here
The idea i had was to set a setState but i still lack the right approach, does anyone have an example or a hint?
I would be very grateful.

Using the Firebase SDK you can get a Stream of data for your Document (Room) by calling onSnapshot() with firebase_cloudstore. The Stream will always output the latest value from firebase. You don't necessarily have to have a StatefulWidget, instead you can use a StatelessWidget with a StreamBuilder and you can put your if logic inside of the StreamBuilders builder method.
Make sure to check that the snapshot hasData and show the appropriate widget.

You can use streambuilder and listen snapshot as
import 'package:flutter/material.dart';
class App extends StatelessWidget {
#override
Widget build(context) {
return StreamBuilder(
stream: Firestore.instance
.collection('roomCollectionName')
.document('roomId')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
);
} else {
if (snapshot.data['roomstart']) {
//true
return Container();
} else {
//false
return Container();
}
}
},
);
}
}

Related

Trouble initializing a <Position> variable in Flutter LateInitializationError: Field '____ ' has not been initialized

newbie to Flutter. My code runs but encounters a
The following LateError was thrown building
FutureBuilder(dirty, state:
_FutureBuilderState#e1a6f):
LateInitializationError: Field 'initialPosition' has not been
initialized.
The code is to set up a GoogleMap widget that takes initial position from the device. I get the red screen with that error, but after a few seconds the coordinates gets received and proceeds as normal and displays the map and position correctly.
Tried future as well but but I get other errors. Is it supposed to be under the FutureBuilder? In a wrapper.dart or my main.dart?
home.dart:
import 'package:flutter/material.dart';
import 'package:something/services/auth.dart';
import 'screens/map.dart';
import 'package:something/services/geolocator_service.dart';
class LakoApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<LakoApp> {
final AuthService _auth = AuthService();
final _geolocatorService = GeolocatorService();
late var initialPosition;
// #override
Future getInitialPosition <Position>() async {
initialPosition = await _geolocatorService.getInitialLocation();
return initialPosition;
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: FittedBox(
child: Text('Something something'),
),
actions: <Widget>[
// irrelevant code
// .....
],
body:
FutureBuilder(
future: getInitialPosition(),
builder: (context, snapshot) {
return Map(initialPosition);
}
)
);
}
}
Future Builders are built even before getting the data. So, you should check whether it has data.
if (snapshot.hasData) {
return Map(initialPosition); //Or snapshot.data.
}else{
return CircularProgressIndicator();
}
There are other problems here. I will show some further code to improve your own code.
Your method returns a Future of any type receiving a generic parameter called Position. I think you want to use a data type called position for that you need to move <Position> here as right now the way you are writing it is useless for your specific example.
Future<Position> getInitialPosition () async {
initialPosition = await _geolocatorService.getInitialLocation();
return initialPosition;
}
The FutureBuilder can be like this.
FutureBuilder<Position>(
future: getInitialPosition(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Map(snapshot.data);
}else{
return CircularProgressIndicator();
//Display loading, you may adapt this widget to your interface or use some state management solution
}
}
)
Edited the code according to suggestions: got rid of the method and variable, because its redundant
body: FutureBuilder <Position> (
future: _geolocatorService.getInitialLocation(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Map(snapshot.data!);
}else {
return Loading();

Flutter - Using GetIt with BuildContext

I'm using Localizations in my app based on the flutter documentation.
See here: https://flutter.dev/docs/development/accessibility-and-localization/internationalization
I use get_it package (version 4.0.4) to retrieve singleton objects like the Localization delegate. Unfortunately it needs a BuildContext property. Sometimes in my app I don't have the context reference so it would be nice if it would work like this: GetIt.I<AppLocalizations>() instead of this: AppLocalizations.of(context). It still can be achieved without a problem if you setup get_it like this: GetIt.I.registerLazySingleton(() => AppLocalizations.of(context)); The problem is that you need the context at least once to make it work. Moreover if you would like to display a localized text instantly in your initial route it's more difficult to get a properly initialized BuildContext at a time when you need it.
It's a little hard for me to explain it properly so I recreated the issue in a minimal example.
I commented out some code that would cause compile time errors, but it shows how I imagined it to be done.
main.dart
GetIt getIt = GetIt.instance;
void setupGetIt() {
// How to get BuildContext properly if no context is available yet?
// Compile time error.
// getIt.registerLazySingleton(() => AppLocalizations.of(context));
}
void main() {
setupGetIt();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
// The above line also won't work. It has BuildContext but Applocalizations.of(context) won't work
// because it's above in the Widget tree and not yet setted up.
getIt.registerLazySingleton(() => AppLocalizations.of(context));
return MaterialApp(
supportedLocales: const [
Locale('en', 'US'),
Locale('hu', 'HU'),
],
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
localeResolutionCallback: (locale, supportedLocales) {
// check if locale is supported
for (final supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale?.languageCode &&
supportedLocale.countryCode == locale?.countryCode) {
return supportedLocale;
}
}
// if locale is not supported then return the first (default) one
return supportedLocales.first;
},
// You may pass the BuildContext here for Page1 in it's constructor
// but in a more advanced routing case it's not a maintanable solution.
home: Page1(),
);
}
}
Initial route
class PageBase extends StatelessWidget {
final String title;
final Widget content;
PageBase(this.title, this.content);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: content,
);
}
}
class Page1 extends PageBase {
// It won't run because I need the context but clearly I don't have it.
// And in a real app you also don't want to pass the context all over the place
if you have many routes to manage.
Page1(String title)
: super(AppLocalizations.of(context).title, Center(child: Text('Hello')));
// Intended solution
// I don't know how to properly initialize getIt AppLocalizations singleton by the time
// it tries to retrieve it
Page1.withGetIt(String title)
: super(getIt<AppLocalizations>().title, Center(child: Text('Hello')));
}
locales.dart
String globalLocaleName;
class AppLocalizations {
//AppLocalizations(this.localeName);
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
static Future<AppLocalizations> load(Locale locale) async {
final String name =
locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
final String localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
globalLocaleName = localeName;
return AppLocalizations();
});
}
String get title => Intl.message(
'This is the title.',
name: 'title',
);
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
// This delegate instance will never change (it doesn't even have fields!)
// It can provide a constant constructor.
const _AppLocalizationsDelegate();
#override
bool isSupported(Locale locale) {
return ['en', 'hu'].contains(locale.languageCode);
}
#override
Future<AppLocalizations> load(Locale locale) => AppLocalizations.load(locale);
#override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
And some intl generated dart code and .arb files that is not so important to illustrate the problem.
So all in all, how can I achive to use my AppLocalizations class as a singleton without using a context for example in a situation like this? Maybe my initial approach is bad and it can be done in other ways that I represented. Please let me know if you have a solution.
Thank you.
To achieve what you have described you need to first make the navigation service using get_it. Follow these steps to achieve the result :
1. Create a navigation service
import 'package:flutter/material.dart';
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey =
new GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState!
.push(routeName);
}
goBack() {
return navigatorKey.currentState!.pop();
}
}
This allows you to navigate anywhere from any point throughout the app without build context. This navigator key is what you can use to achieve the AppLocalization instance for the current context.
Refer to the FilledStacks tutorials for this method of navigating without build context.
https://www.filledstacks.com/post/navigate-without-build-context-in-flutter-using-a-navigation-service/
2. Register
GetIt locator = GetIt.instance;
void setupLocator() {
...
locator.registerLazySingleton(() => NavigationService());
...
}
3. Assign the navigator key in the material app
return MaterialApp(
...
navigatorKey: navigationService.navigatorKey,
...
),
3. Create an instance for the AppLocalizations and import it wherever you want to use
localeInstance() => AppLocalizations.of(locator<NavigationService>().navigatorKey.currentContext!)!;
3. The actual use case
import 'package:{your_app_name}/{location_to_this_instace}/{file_name}.dart';
localeInstance().your_localization_variable
You can add a builder to your MaterialApp and setup the service locator inside it with the context available. Example:
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, widget) {
setUpServiceLocator(context);
return FutureBuilder(
future: getIt.allReady(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return widget;
} else {
return Container(color: Colors.white);
}
});
},
);
}
Service Locator Setup:
void setUpServiceLocator(BuildContext context) {
getIt.registerSingleton<AppLocalizations>(AppLocalizations.of(context));
}
You could use some non-localizable splash screen with FutureBuilder and getIt.allReady().
Something like:
class SplashScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder<void>(
future: getIt.allReady(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// Navigate to main page (with replace)
} else if (snapshot.hasError) {
// Error handling
} else {
// Some pretty loading indicator
}
},
);
}
I'd like to recommend the injectable package for dealing with get_it also.

Navigator.pop from a FutureBuilder

I have a first screen which ask the user to enter to input, then when the users clicks on a button, the app goes on a second screen which uses a FutureBuilder to call an API.
If the API returns an error, I would like to go back to the previous screen with Navigator.pop. When I try to do that in the builder of the FutureBuilder, I get an error because I modify the tree while I am building it...
setState() or markNeedsBuild() called during build. This Overlay
widget cannot be marked as needing to build because the framework is
already in the process of building widgets
What is the proper way to go to the previous screen if an error occur?
class Stackoverflow extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<Flight>(
future: fetchData(context),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ScreenBody(snapshot.data);
} else if (snapshot.hasError) {
Navigator.pop(context, "an error");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
)
),
);
}
}
PS: I tried to use addPostFrameCallback and use the Navigator.pop inside, but for some unknown reason, it is called multiple times
You can not directly navigate when build method is running, so it better to show some error screen and give use chance to go back to last screen.
However if you want to do so then you can use following statement to do so.
Future.microtask(() => Navigator.pop(context));
I'd prefer to convert class into StateFullWidget and get rid of FutureBuilder
class Stackoverflow extends StatefulWidget {
#override
_StackoverflowState createState() => _StackoverflowState();
}
class _StackoverflowState extends State<Stackoverflow> {
Flight flight;
#override
void initState() {
super.initState();
fetchData().then((data) {
setState(() {
flight = data;
});
}).catchError((e) {
Navigator.pop(context, "an error");
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: flight != null ? ScreenBody(flight) : CircularProgressIndicator(),
),
);
}
}
and of cause pass context somewhere outside class is not good approach

FutureBuilder runs twice

I have problems with FutureBuilder starting twice.
First it fetch the data correctly, returning my StartScreen, then after few seconds, the StartScreen rebuilds and I noticed that the FutureBuilder fires again.
Here is my code and it's pretty simple, so I wonder what may the problem be?!?
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
FirebaseUser user;
#override
void initState() {
// TODO: implement initState
super.initState();
getNewestlocation();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'APP',
theme: buildTheme(),
home: FutureBuilder<FirebaseUser>(
future: Provider.of<AuthService>(context).getUser(),
builder: (context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.error != null) {
print('error');
return Text(snapshot.error.toString());
}
user = snapshot.data;
print('user here $user');
return snapshot.hasData ? StartScreen(user) : LoginScreen();
} else {
return LoadingCircle();
}
},
),
);
}
}
Can anyone help me with this, please?
The future is firing again because you're creating it in the build method at the same time as the FutureBuilder.
From the FutureBuilder docs:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateConfig, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
So to prevent it from firing you'd have to do something like this:
class _MyAppState extends State<MyApp> {
Future<String> _myString;
#override
void initState() {
super.initState();
_myString = _fetchString();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _myString,
builder: (context, snapshot) {
// build page stuff...
},
),
);
}
}
Future<String> _fetchString() async {
print('running future function');
await Future.delayed(Duration(seconds: 3));
return 'potatoes';
}
Note, to access a provider in initState() you have to set listen to false, as detailed in this answer.
I think you have some things bad in your code, maybe that's not the problem but is good to correct that:
first: It is not recommendable to do that job in your main file, you should have something like a Splash page to handle that.
second: You should use blocs and not write your logic code on the same place at the view(UI)
If you're using android studio, try if running from the terminal fix the issue. The run button attached the debug service, which then force the entire app to be rebuilt

How to pass setstate as a parameter to a class method

in flutter I use a class to load values for switch widgets from a database and then update that database when the switch is toggled. Somehow I need to have that class call setstate on the calling function of the instance but it doesn't seem to work.
See the code below for an example.
The first switch is how I'd write it without the database class. That is working fine, when tapping the switch it both moves and the print shows that the value changed.
In the second switch widget however, I used the database class to build it but it doesn't seem to call the callback function correctly. The print always prints false.
I thought I tried all combinations of => and (){} but something is still amiss. I'm pretty sure the problem is how the callback is handled in the line: callBackFunctionForSetState();
maybe that should be called with callBackFunctionForSetState((){}); but that also doesn't work.
import 'package:flutter/material.dart';
void main() {
runApp(App());
}
bool myBool = true;
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Title',
home: ScreenUpgrades(),
);
}
}
class ScreenUpgrades extends StatefulWidget {
#override
_ScreenUpgradesState createState() => _ScreenUpgradesState();
}
class _ScreenUpgradesState extends State<ScreenUpgrades> {
#override
Widget build(BuildContext ctxt) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Upgrades"),
),
body: FutureBuilder(
future: buildSwitchList(),
builder: (BuildContext ctxt, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView(children: snapshot.data);
} else {
return Center(child: CircularProgressIndicator());
}
}));
}
Future<List> buildSwitchList() async {
List<Widget> widgetList = [];
//This one below for a working example only
widgetList.add(Switch(value: myBool,onChanged: (bb)=>nonDBSetState()));
//Normally I'll create a bunch of widgets by loading their data from the DB as below
widgetList.add(DataBaseSwitchBuilder(1,()=>setState((){})).listViewWidget);
return widgetList;
}
nonDBSetState()
{
myBool = !myBool;
print('New value of first switch: ' + myBool.toString());
setState((){});
}
}
class DataBaseSwitchBuilder {
Widget listViewWidget;
int dbID;
bool onOff;
Function callBackFunctionForSetState;
DataBaseSwitchBuilder (int paramID, Function callBack)
{
dbID=paramID; //used to query the parameter from the DB
onOff = true;
callBackFunctionForSetState=callBack;
listViewWidget=(Switch(value: onOff,onChanged: (bb)=> updateDBAndState()));
}
updateDBAndState()
{
//update the switch
onOff = !onOff;
print('DB Swtich value now: ' + onOff.toString());
//first we save the record in the DB
//todo: code for updating DB
//Then call the passed function which should be a setstate from the calling function
//Below doesn't seem to work.
callBackFunctionForSetState();
}
}
I'm just expecting that the updateDBAndState will allow me to save the new value of the switch to the database and then call the setstate callback.
Just to respond to "How to pass setstate as a parameter to a class method"
widget controler
class Controller {
Controller._privateConstructor();
factory Controller() => _singleton;
static final Controller _singleton =
Controller._privateConstructor();
late Function setStateHandler;
void initSetState(Function setState) => setStateHandler = setState;
void triggerSetState() => setStateHandler();
}
widget
#override
void initState() {
super.initState();
controller.initSetState(() => setState(() {
widgetVariable = true;
}));
}