Change UI based on change to provided value - flutter

I have a screen from which I want to navigate to a new screen if a provided boolean value(isLoggedIn) gets changed to true. The first build method below is not possible since Navigator cannot be called during build. I attempted to do this in build because I need access to context to use my provider. The value of isLoggedIn coming from the provider of Authorization can potentially change at any time, so I would have to check for this.
How can I solve this using provider and navigator?
//Not working solution
Widget build(BuildContext context) {
Authorization auth = Provider.of<Authorization>(context);
return Scaffold(
body: Center(
child: auth.isLoggedIn
? Text(
"Logged In",
)
: Navigator.pushReplacementNamed(context, 'sign-in')),
);
}
I can do it without Navigator like this:
//Working solution
class SplashScreen extends StatelessWidget {
static const routeName = 'splash';
#override
Widget build(BuildContext context) {
final auth = Provider.of<Authorization>(context);
return auth.isLoggedIn ? HomeScreen() : SignInScreen();
}
}
But I do not now if that is a solid approach.

Here is an approach to solving your problem.
enum AuthStatus {
NOT_DETERMINED,
NOT_LOGGED_IN,
LOGGED_IN,
}//Always define this outside the class.
AuthStatus authStatus = AuthStatus.NOT_DETERMINED;
#override
void initState() {
super.initState();
getCurrentUser().then((user) {
setState(() {
if (user != null) {
_userId = user?.uid;
}
authStatus =
user?.uid == null ? AuthStatus.NOT_LOGGED_IN : AuthStatus.LOGGED_IN;
});
});
}
Future<FirebaseUser> getCurrentUser() async {
FirebaseUser user = await _firebaseAuth.currentUser();
return user;
}
void loginCallback() {
getCurrentUser().then((user) {
setState(() {
_userId = user.uid.toString();
authStatus = AuthStatus.LOGGED_IN;
Navigator.of(context).pushReplacementNamed('/');
});
});
}
Widget buildWaitingScreen() {
return Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: CircleAvatar(
backgroundColor: Colors.transparent,
radius: 70.0,
child: Image.asset('assets/icons/icon.png'),
),
),
);
}
#override
Widget build(BuildContext context) {
switch (authStatus) {
case AuthStatus.NOT_DETERMINED:
return buildWaitingScreen();
break;
case AuthStatus.NOT_LOGGED_IN:
return new LoginSignUpPage();
break;
case AuthStatus.LOGGED_IN:
if (_userId.length > 0 && _userId != null) {
return new HomePage();
} else
return buildWaitingScreen();
break;
default:
return buildWaitingScreen();
}
}
}
To navigate use this in initState
void loginCallback() {
getCurrentUser().then((user) {
setState(() {
_userId = user.uid.toString();
authStatus = AuthStatus.LOGGED_IN;
});});
if(authStatus==AuthStatus.LOGGED_IN){
Navigator.pushReplacementNamed(context, 'sign-in')} }

Related

Show flutter overlay according to different variables

I have a flutter screen called TestMain which has a scaffold and white background. The scaffolds body is supposed to change if certain events happen. The events are stored as a boolean. There is "isLocked" and "isPaused" which get emitted by a Riverpod Stream Provider and "isCheating" which changes when Applifecyle events get triggered. All of the three booleans are stored as Riverpod StateProviders, because of its global accesibility.
This is is my "isCheatingProvider":
final isCheatingProvider = StateProvider.autoDispose<bool>((ref) => false);
The "isPausedProvider" and "isLockedProvider" are the same.
This is the TestMain screen
class TestMain extends ConsumerStatefulWidget {
const TestMain({super.key});
#override
ConsumerState<TestMain> createState() => _TestMainScreenState();
}
class _TestMainScreenState extends ConsumerState<TestMain>
with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
final isCheating = ref.watch(isCheatingProvider.notifier);
switch (state) {
case AppLifecycleState.resumed:
case AppLifecycleState.inactive:
await sendCheatingAttempt(ref);
setState(() {
isCheating.state = true;
});
break;
case AppLifecycleState.paused:
await sendCheatingAttempt(ref);
setState(() {
isCheating.state = true;
});
break;
case AppLifecycleState.detached:
await sendCheatingAttempt(ref);
setState(() {
isCheating.state = true;
});
break;
}
}
#override
Widget build(BuildContext context) {
final List<Item> items = ref.watch(itemsProvider);
final AsyncValue<dynamic> wsTestListenerMessage =
ref.watch(testListenerProvider);
final isLocked = ref.watch(isLockedProvider.notifier);
final isPaused = ref.watch(isPausedProvider.notifier);
final isCheating = ref.watch(isCheatingProvider.notifier);
wsTestListenerMessage.when(
loading: () => {},
error: (err, stack) => print('Test State Error: $err'),
data: (message) async {
Future.delayed(const Duration(seconds: 0), () {
if (message["lock"] == true) {
isLocked.state = true;
}
if (message["unlock"] == true) {
isLocked.state = false;
}
if (message["paused"] == true) {
isPaused.state = true;
}
if (message["resumed"] == true) {
isPaused.state = false;
}
});
},
);
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: isPaused.state
? const ErrorOverlay(text: 'paused')
: isLocked.state || isCheating.state
? const ErrorOverlay(text: 'cheating')
: const TestView()),
);
}
}
But it doesnt work. No matter what I do. I added the Future.delayed(const Duration(seconds: 0), () {} around the if-statements, because it complained about changing the provider in build method, I use setState() in didChangeAppLifecycleState(), but can't use it in the listener, because the listener would called over and over again. It shouldnt be openend more than once.
(ErrorOverlay is a custom widget that just shows the text in big red letters, in the center)
remove the setState, this will do nothing
for set a state use ref.read(provider.notifier).state
for watch use ref.watch(isCheatingProvider)
By changing all that it is good by testing on my side :
final isCheatingProvider = StateProvider.autoDispose<bool>((ref) => false);
class TestMain extends ConsumerStatefulWidget {
const TestMain({key});
#override
ConsumerState<TestMain> createState() => _TestMainScreenState();
}
class _TestMainScreenState extends ConsumerState<TestMain>
with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
final isCheating = ref.read(isCheatingProvider.notifier);
switch (state) {
case AppLifecycleState.resumed:
case AppLifecycleState.inactive:
isCheating.state = true;
break;
case AppLifecycleState.paused:
isCheating.state = true;
break;
case AppLifecycleState.detached:
isCheating.state = true;
break;
}
}
#override
Widget build(BuildContext context) {
final isCheating = ref.watch(isCheatingProvider);
return Scaffold(
backgroundColor: isCheating ? Colors.red : Colors.white,
body: SafeArea(
child: isCheating ? Text('cheat') : Text(' good')
)
);
}
}
You are incorrectly using StateProvider. To watch StateNotifier you should use
final isCheating = ref.watch(isCheatingProvider);
and to change provider use
ref.read(productSortTypeProvider.notifier).state = value;
So you have to change all provider related code.
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
final isCheatingNotifier = ref.read(isCheatingProvider.notifier);
switch (state) {
case AppLifecycleState.resumed:
case AppLifecycleState.inactive:
await sendCheatingAttempt(ref);
isCheatingNotifier.state = true;
break;
case AppLifecycleState.paused:
await sendCheatingAttempt(ref);
isCheatingNotifier.state = true;
break;
case AppLifecycleState.detached:
await sendCheatingAttempt(ref);
isCheatingNotifier.state = true;
break;
}
}
#override
Widget build(BuildContext context) {
final List<Item> items = ref.watch(itemsProvider);
final AsyncValue<dynamic> wsTestListenerMessage =
ref.watch(testListenerProvider);
final isLocked = ref.watch(isLockedProvider);
final isPaused = ref.watch(isPausedProvider);
final isCheating = ref.watch(isCheatingProvider);
wsTestListenerMessage.when(
loading: () => {},
error: (err, stack) => print('Test State Error: $err'),
data: (message) async {
Future.delayed(const Duration(seconds: 0), () {
final isLockedNotifier = ref.read(isLockedProvider.notifier);
final isPausedNotifier = ref.read(isPausedProvider.notifier);
if (message["lock"] == true) {
isLockedNotifier.state = true;
}
if (message["unlock"] == true) {
isLockedNotifier.state = false;
}
if (message["paused"] == true) {
isPausedNotifier.state = true;
}
if (message["resumed"] == true) {
isPausedNotifier.state = false;
}
});
},
);
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: isPaused
? const ErrorOverlay(text: 'paused')
: isLocked || isCheating
? const ErrorOverlay(text: 'cheating')
: const TestView()),
);
}

Flutter prevent building a new widget if already in tree

For some reason, the Build method is called twice which results in two MainContent widgets being created. The problem is that in one of my widgets a Listener displays messages to the user according to certain actions. Because Maincontent is duplicated, messages are displayed twice.
widget tree
How to prevent the MainContent widget from being duplicated?
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Bloc.observer = ProductBlocObserver();
var productStorage = ProductStorage();
await productStorage.products().then((localProducts) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('isMember', false);
await prefs.setBool('membershipError', false);
await prefs.setBool('firstOpen', true);
await prefs.setString('memberFirstName', '');
await prefs.setString('token', '');
runApp(App(list: localProducts));
});
}
class App extends StatelessWidget {
List<LocalProductEntity> list;
App({Key? key, required this.list}) : super(key: key) {
list = list;
}
#override
Widget build(BuildContext context) {
return BlocProvider <ListBloc>(
create: (_) => ListBloc(list: list),
child: MaterialApp(
debugShowCheckedModeBanner: true,
title: constants.appTitle,
theme: ThemeData(
colorScheme: ColorScheme.fromSwatch().copyWith(
primary: Color(ColorsLNC.green5),
secondary: Color(ColorsLNC.green1),
),
),
home: BlocPage()
),
);
}
}
class BlocPage extends StatelessWidget {
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
late Future<bool> isMember;
late Future<bool> membershipError;
late Future<bool> firstOpen;
late Future<String> memberFirstName;
late Future<String> token;
late BuildContext context;
BlocPage({super.key});
#override
Widget build(BuildContext context) {
this.context = context;
isMember = _prefs.then((SharedPreferences prefs) {
return prefs.getBool('isMember') ?? false;
});
membershipError = _prefs.then((SharedPreferences prefs) {
return prefs.getBool('membershipError') ?? false;
});
firstOpen = _prefs.then((SharedPreferences prefs) {
return prefs.getBool('firstOpen') ?? false;
});
memberFirstName = _prefs.then((SharedPreferences prefs) {
return prefs.getString('memberFirstName') ?? '';
});
token = _prefs.then((SharedPreferences prefs) {
return prefs.getString('token') ?? '';
});
return FutureBuilder(
future: membershipFlow(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
default:
return snapshot.data;
}
});
}
Future<Widget> membershipFlow() async {
if (await isMember == true && await membershipError == false) {
return SplashPage(
mainContext : context,
goToPage: MainContent(),
text: await memberFirstName,
duration: 2,
);
} else if (await isMember == false && await membershipError == false) {
return const MembershipForm();
} else if (await isMember == false && await membershipError == true) {
return ErrorPage(text: Babel.translate(key: 'E_WRONG_MEMBERSHIP'));
} else {
return ErrorPage(text: Babel.translate(key: 'E_UNEXPECTED'));
}
}
}
Use const with both constructor and while using it.
For example
const BlocPage({super.key}) and const BlocPage().
You code has different issue tough. You could refactor your code like this and build won't be called repeatedly.
class BlocPage extends StatelessWidget {
const BlocPage({super.key});
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: membershipFlow(context),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
default:
return snapshot.data;
}
});
}
Future<Widget> membershipFlow(BuildContext context) async {
final prefs = await SharedPreferences.getInstance();
final isMember = prefs.getBool('isMember') ?? false;
final membershipError = prefs.getBool('membershipError') ?? false;
final firstOpen = prefs.getBool('firstOpen') ?? false;
final memberFirstName = prefs.getString('memberFirstName') ?? '';
final token = prefs.getString('token') ?? '';
if (isMember == true && membershipError == false) {
return SplashPage(
mainContext : context,
goToPage: MainContent(),
text: memberFirstName,
duration: 2,
);
} else if (isMember == false && membershipError == false) {
return const MembershipForm();
} else if (isMember == false && membershipError == true) {
return ErrorPage(text: Babel.translate(key: 'E_WRONG_MEMBERSHIP'));
} else {
return ErrorPage(text: Babel.translate(key: 'E_UNEXPECTED'));
}
}
}

How to move classes and functions to a separate file in Flutter/Dart?

Good day! I am new to Flutter/Dart. And the more I experiment, the bigger my main file gets. Obviously, I need a separate file in which I will store all the classes and functions that I will refer to in the future.
I have a separate screen with what I need. Here is its code:
//Internet route
class InternetRoute extends StatefulWidget {
const InternetRoute({Key? key}) : super(key: key);
#override
State<InternetRoute> createState() => _InternetRouteState();
}
class _InternetRouteState extends State<InternetRoute> {
bool ActiveConnection = false;
String T = "";
Future CheckUserConnection() async {
try {
final result = await InternetAddress.lookup('example.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
setState(() {
ActiveConnection = true;
T = "Turn off the data and repress again";
});
}
} on SocketException catch (_) {
setState(() {
ActiveConnection = false;
T = "Turn On the data and repress again";
showInternetDialog(context);
});
}
}
#override
void initState() {
CheckUserConnection();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("GeeksforGeeks"),
),
body: Column(
children: [
Text("Active Connection? $ActiveConnection"),
const Divider(),
Text(T),
OutlinedButton(
onPressed: () {
CheckUserConnection();
},
child: const Text("Check"))
],
),
);
}
}
//Alert Dialog about Internet connection
showInternetDialog(BuildContext context) {
// set up the button
Widget okButton = Center(
child: TextButton(
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop(); // dismiss dialog
},
),
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
// title: Text("My title"),
content: Text("Internet connection required"),
actions: [
okButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
I want to create a my classes.dart file that will gradually populate with the most commonly used things. In particular, I need class _InternetRouteState and showInternetDialog.
How to transfer them to a new file? I completely copied the code of that screen. Is it correct? Would that be enough to then refer to them in main.dart (after import)? Will all their variables be visible to my screens as well?
Edit 1. I don't know how I can move CheckUserConnection to my file. I mean I took the piece of code I needed and wrapped it in the CheckUserConnection class (in my separate file), but it doesn't work. What am I doing wrong?
class CheckUserConnection {
bool ActiveConnection = false;
String T = "";
Future CheckUserConnection() async {
try {
final result = await InternetAddress.lookup('example.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
setState(() {
ActiveConnection = true;
T = "Turn off the data and repress again";
});
}
} on SocketException catch (_) {
setState(() {
ActiveConnection = false;
T = "Turn On the data and repress again";
// showInternetDialog(context); //temporary
});
}
}
}
The Problems tab shows the following errors:
Constructors can't have a return type.
The modifier 'async' can't be applied to the body of a constructor.
The await expression can only be used in an async function.
The method 'setState' isn't defined for the type 'CheckUserConnection'.
The method 'setState' isn't defined for the type 'CheckUserConnection'.
Create a new dart file. Name it internet_dialog_handler.dart. Add this to the file
class InternetDialogHandler{
//Alert Dialog about Internet connection
showInternetDialog(BuildContext context) {
// set up the button
Widget okButton = Center(
child: TextButton(
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop(); // dismiss dialog
},
),
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
// title: Text("My title"),
content: Text("Internet connection required"),
actions: [
okButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
In internetRoute use this
//Internet route
class InternetRoute extends StatefulWidget {
const InternetRoute({Key? key}) : super(key: key);
#override
State<InternetRoute> createState() => _InternetRouteState();
}
class _InternetRouteState extends State<InternetRoute> {
bool ActiveConnection = false;
String T = "";
InternetDialogHandler _internetDialogHandler = InternetDialogHandler();
Future CheckUserConnection() async {
try {
final result = await InternetAddress.lookup('example.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
setState(() {
ActiveConnection = true;
T = "Turn off the data and repress again";
});
}
} on SocketException catch (_) {
setState(() {
ActiveConnection = false;
T = "Turn On the data and repress again";
//Use the variable here to access the method in that class
_internetDialogHandler.showInternetDialog(context);
});
}
}
#override
void initState() {
CheckUserConnection();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("GeeksforGeeks"),
),
body: Column(
children: [
Text("Active Connection? $ActiveConnection"),
const Divider(),
Text(T),
OutlinedButton(
onPressed: () {
CheckUserConnection();
},
child: const Text("Check"))
],
),
);
}
}
EDIT
class CheckUserConnection {
Future checkInternetAvailability() async {
try {
final result = await InternetAddress.lookup('example.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
return true;
}
} on SocketException catch (_) {
return false;
}
}
}
Use a different name for the method. Same name is used to defined the constructor of the class. Also make it work independent. Just return a true or false. Now to use this define a variable of type checkUserConnection
CheckUserConnection _checkUserConnection = CheckUserConnection();
bool _internetAvailable = await _checkUserConnection.checkInternetAvailability();
if(_internetAvailable)
{
//do something here;
} else{
//handle no internet here
}

Why my app (developed using ChangeNotifierProvider) updates with one level delay?

I'm developing a flutter app and I've used ChangeNotifierProvider to manage states.I have a class called 'Data' which is the model of the app (model in MVC design pattern) and a class called 'DataManager' which is the controller of the app (controller in MVC design pattern) and makes an instance of Data in it.
I've made instance of ChangeNotifierProvider in my main and the ChangeNotifier is DataManager. So the methods in DataManager call notifyListeners() method.
When I run the app, I add a tile and the UI it won't change although the tile is added. After I add another tile the first one appears and so on.The app is always one level behind the user.
Can you help me fix this problem?
This is main.dart:
main(){
runApp(MyApp());
}
class MyApp extends StatefulWidget{
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => DataManager(),
child: MaterialApp(
home: LoadingScreen()
),
);
}
}
This is Data.dart (It's methods might not be important here):
class Data{
Position _location;
List<CityTile> _cityWidgets = List<CityTile>();
List<Weather> _weatherDatas = List<Weather>();
Future<Position> getLocation() async {
bool serviceEnabled;
LocationPermission permission;
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
print('Location services are disabled.');
return Future.error('Location services are disabled.');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.deniedForever) {
print('Location permissions are permanently denied, we cannot request permissions.');
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.');
}
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission != LocationPermission.whileInUse &&
permission != LocationPermission.always) {
print( 'Location permissions are denied (actual value: $permission).');
return Future.error(
'Location permissions are denied (actual value: $permission).');
}
}
_location = await Geolocator.getCurrentPosition();
return _location;
}
void addCity({Weather cityWeather}) async{
bool isReady = await cityWeather.updateWeather();
String cityName = cityWeather.getCity;
if(!cityExists(cityName) && isReady) {
_weatherDatas.add(cityWeather);
_cityWidgets.add(CityTile(cityName));
}
// print("widgets:");
// for(CityTile cityTile in _cityWidgets){
// print("${cityTile.city} widget exists");
// }
// print("weathers:");
// for(Weather weather in _weatherDatas){
// print("${weather.getCity} weather exists");
// }
}
Weather searchWeather({String cityName}){
for(Weather weather in _weatherDatas){
if(weather.getCity == cityName){
return weather;
}
}
return null;
}
bool cityExists(String cityName){
if(searchWeather(cityName: cityName) == null)
return false;
else
return true;
}
void removeCity({String cityName}) {
if (cityExists(cityName)) {
_removeCityWidget(cityName: cityName);
_removeCityWeather(cityName: cityName);
}
}
void _removeCityWidget({String cityName}){
CityTile cityTileToRemove;
for(CityTile cityTile in _cityWidgets){
if(cityTile.city == cityName){
cityTileToRemove = cityTile;
}
}
if(cityTileToRemove != null)
_cityWidgets.remove(cityTileToRemove);
}
void _removeCityWeather({String cityName}){
Weather weather = searchWeather(cityName: cityName);
if(weather != null)
_weatherDatas.remove(weather);
}
int widgetNumbers(){
return _cityWidgets.length;
}
get weatherDatas{
return List.unmodifiable(_weatherDatas);
}
get cityWidgets{
return List.unmodifiable(_cityWidgets);
}
}
This is DataManager.dart:
class DataManager extends ChangeNotifier{
Data data = Data();
Future<bool> findWeatherByLocation() async{
Position location = await data.getLocation();
// print("long : ${location.longitude} and lat : ${location.latitude}");
Weather weatherOfHere = Weather(city: null);
String weatherCast = "";
if(location == null){
// print("location is null");
return false;
}
for(int i=0; i<5; i++){
weatherCast = await weatherOfHere.getCurrentWeather(location: location);
if(weatherCast.isNotEmpty)
break;
}
if( weatherCast.isEmpty || jsonDecode(weatherCast)['cod'] == '404') {
// print("city not found");
return false;
}
// print("weathercast : $weatherCast");
addCityByWeather(weatherOfHere);
return true;
}
void addCityByWeather(Weather cityWeather){
data.addCity(cityWeather: cityWeather);
notifyListeners();
}
void addCityByName(String city) async{
if(!data.cityExists(city) && city.isNotEmpty){
Weather cityWeather = Weather(city: city);
bool isRealCity = await cityWeather.updateWeather();
if(isRealCity) {
data.addCity(cityWeather: cityWeather);
}
}
notifyListeners();
}
void removeCity(String city){
data.removeCity(cityName: city);
notifyListeners();
}
int cityNumbers(){
return data.widgetNumbers();
}
Future<bool> updateWeather(String city) async{
Weather weatherToUpdate = data.searchWeather(cityName: city);
bool isUpdated = false;
if(weatherToUpdate == null){
return false;
}
else{
isUpdated = await weatherToUpdate.updateWeather();
notifyListeners();
}
return isUpdated;
}
get weatherDatas{
return data.weatherDatas;
}
get cityWidgets{
return data.cityWidgets;
}
void addOption(String option){
option = option.toLowerCase() == 'feels like' ? 'feels_like' : option;
options[option.toLowerCase()] = true;
//updateAll();
notifyListeners();
}
void removeOption(String option){
option = option.toLowerCase() == 'feels like' ? 'feels_like' : option;
options[option.toLowerCase()] = false;
// updateAll();
notifyListeners();
}
void updateAll(){
for(Weather weather in data.weatherDatas)
weather.updateWeather();
notifyListeners();
}
bool isOptionSelected(String option){
option = option.toLowerCase() == 'feels like' ? 'feels_like' : option;
// print("in isOptionSelected: ${options[option.toLowerCase()]}");
return options[option.toLowerCase()];
}
Color getOptionButtonColor(String option){
option = option.toLowerCase() == 'feels like' ? 'feels_like' : option;
return isOptionSelected(option) ? Colors.indigo : Colors.black38;
}
get getOptions{
return options;
}
String getWeatherScreenPicture(String city){
Weather weatherData = data.searchWeather(cityName: city);
int id = weatherData.id;
if(id == 800){
var now = new DateTime.now();
List clearSky = codeToPicture[800];
if( now.hour> 18 ) {
return clearSky[1];
}else
return clearSky[0];
}
return codeToPicture[id];
}
String getWeatherInfo(String city, String field){
Weather weather = data.searchWeather(cityName: city);
if(weather != null){
switch(field){
case 'temperature':
return weather.temperature;
case 'pressure':
return weather.pressure;
case 'humidity':
return weather.humidity;
case 'weather description':
return weather.weatherDescription;
case 'wind speed':
return weather.windSpeed;
case 'feels_like':
return weather.feelsLike;
}
}
return "null";
}
IconData getWeatherIcon(String city){
Weather weather = data.searchWeather(cityName: city);
if(weather != null)
return weather.icon;
else
return WeatherIcons.refresh;
}
}
There is also a listView.Builder which adds these tiles( city widgets ):
class CitiesScreen extends StatelessWidget {
final TextEditingController _textEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: NavigationBar(),
backgroundColor: Colors.lightBlue,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: Column(
children: <Widget>[
ColorfulBox(
ListTile(
title: TextField(
controller: _textEditingController,
style: TextStyle(fontSize: 20, color: Colors.white),),
trailing: SizedBox(
width: 100,
child: Row(
children: [
SizedBox(
width: 50,
child: FlatButton(
child: Icon(Icons.add, size: 30, color: Colors.white,),
onPressed: () {
Provider.of<DataManager>(context, listen: false).addCityByName(_textEditingController.text);
_textEditingController.clear();
},
),
),
SizedBox(
width: 50,
child: FlatButton(
onPressed: () => Provider.of<DataManager>(context, listen: false).findWeatherByLocation(),
child: Icon(Icons.location_on_outlined, size: 30, color: Colors.white,),
),
)
],
),
),
),
),
SizedBox(height: 30,),
Expanded(
child: Consumer<DataManager>(
builder: (context, data, child){
return ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: Provider.of<DataManager>(context).cityNumbers(),
itemBuilder: (context, index) => Provider.of<DataManager>(context).cityWidgets[index],
);
},
),
)
],
),
),
),
);
}
}
Insert
Provider.of<DataManager>(context);
to build Function.
It listens when you call notifyListeners() and updates the UI.

Flutter Snapshot of Streambuilder has no data after condition is met

I have a class Home with a PageView as body and a BottomNavigationBar.
In this class the current user and the current location of the user is loaded.
When the user and the location is known, a global variable is set to true
On the first tab icon of the BottomNavigationBar there is a feed of nearby locations coded in class Feed
Now the issue.
When I start the app for the first time or make a hot reload geoQuery() returns the circular spinner. When the current user is loaded it returns the text "No Data" instead of showing the events. The user needs to change the tab of BottomNavigationBar from feed to something else and back to feed to refresh the streambuilder. After that it works as expected.
When I use the streambuilder without the condition (currentLocationloaded && currentUserloaded == true) it works as expected but sometimes it throws an error as the user is not loaded fast enough.
What can I do to get it work with condition?
Update
Workflow logged in:
RootPage -> Logged in? -> Home
RootPage
enum AuthStatus {
NOT_DETERMINED,
NOT_LOGGED_IN,
LOGGED_IN,
}
class RootPage extends StatefulWidget {
RootPage({this.auth});
final BaseAuth auth;
#override
State<StatefulWidget> createState() => new _RootPageState();
}
class _RootPageState extends State<RootPage> {
AuthStatus authStatus = AuthStatus.NOT_DETERMINED;
String _userID = "";
#override
void initState() {
super.initState();
widget.auth.getCurrentUser().then((user) {
setState(() {
if (user != null) {
_userID = user?.uid;
}
authStatus =
user?.uid == null ? AuthStatus.NOT_LOGGED_IN : AuthStatus.LOGGED_IN;
});
});
}
void loginCallback() {
widget.auth.getCurrentUser().then((user) {
setState(() {
_userID = user.uid.toString();
});
});
setState(() {
authStatus = AuthStatus.LOGGED_IN;
});
}
void logoutCallback() {
setState(() {
authStatus = AuthStatus.NOT_LOGGED_IN;
_userID = "";
});
}
Widget buildWaitingScreen() {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: CircularProgressIndicator(),
),
);
}
#override
Widget build(BuildContext context) {
switch (authStatus) {
case AuthStatus.NOT_DETERMINED:
return buildWaitingScreen();
break;
case AuthStatus.NOT_LOGGED_IN:
return new StartPage(
auth: widget.auth,
loginCallback: loginCallback,
);
break;
case AuthStatus.LOGGED_IN:
if (_userID.length > 0 && _userID != null) {
return new Home(
userID: _userID,
auth: widget.auth,
logoutCallback: logoutCallback,
);
} else
return buildWaitingScreen();
break;
default:
return buildWaitingScreen();
}
}
}
Home
User currentUser;
bool currentUserloaded = false;
bool currentLocationloaded = false;
class Home extends StatefulWidget {
final BaseAuth auth;
final VoidCallback logoutCallback;
final String userID;
const Home({Key key, this.auth, this.logoutCallback, this.userID})
: super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
PageController pageController;
int pageIndex = 0;
double longitude;
double latitude;
//INIT
#override
void initState() {
super.initState();
loadCurrentUser();
getCurrentLocation();
pageController = PageController();
}
//LOAD current user
loadCurrentUser() async {
print("Current User ${widget.userID}");
DocumentSnapshot doc = await userRef.document(widget.userID).get();
currentUser = User.fromDocument(doc);
setState(() {
currentUserloaded = true;
print("User loaded $currentUserloaded");
});
}
//get current location
getCurrentLocation() async {
var currentLocationCoordinates = await Geolocator()
.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
List<Placemark> place = await Geolocator().placemarkFromCoordinates(
currentLocationCoordinates.latitude,
currentLocationCoordinates.longitude);
latitude = currentLocationCoordinates.latitude;
longitude = currentLocationCoordinates.longitude;
setState(() {
currentLocationloaded = true;
print("Got location $currentLocationloaded");
});
}
//DISPOSE
#override
void dispose() {
pageController.dispose();
super.dispose();
}
//Pageview
onPageChanged(int pageIndex) {
setState(() {
this.pageIndex = pageIndex;
});
}
//On Tap of ButtomTabbar => Jump to next Page
onTap(int pageIndex) {
if (currentUserloaded && currentLocationloaded) {
pageController.jumpToPage(pageIndex);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: PageView(
children: <Widget>[
Feed(userID: widget.userID, latitude: latitude, longitude: longitude),
SearchView(),
ChatHome(),
Profile(
uid: currentUser?.uid,
auth: widget.auth,
logoutCallback: widget.logoutCallback),
],
controller: pageController,
onPageChanged: onPageChanged,
physics: NeverScrollableScrollPhysics(),
),
bottomNavigationBar: CupertinoTabBar(
currentIndex: pageIndex,
inactiveColor: Colors.white,
backgroundColor: Colors.blue,
activeColor: Colors.orange,
onTap: onTap,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home, size: 20),
title: Text("Home"),
),
BottomNavigationBarItem(
icon: Icon(Icons.search, size: 20),
title: Text("Search"),
),
BottomNavigationBarItem(
icon: Icon(Icons.chat, size: 20),
title: Text("chat"),
),
BottomNavigationBarItem(
icon: Icon(Icons.profil, size: 20),
title: Text("Profil"),
),
]),
);
}
}
Feed
class Feed extends StatefulWidget {
final String userID;
final double latitude;
final double longitude;
const Feed({Key key, this.userID, this.latitude, this.longitude})
: super(key: key);
#override
_FeedState createState() => _FeedState();
}
class _FeedState extends State<Feed> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
List<Event> events = [];
var radius = BehaviorSubject<double>.seeded(50.0);
Stream<List<DocumentSnapshot>> stream;
Geoflutterfire geo;
#override
void initState() {
super.initState();
geo = Geoflutterfire();
GeoFirePoint center = geo.point(
latitude: widget.latitude,
longitude: widget
.longitude);
stream = radius.switchMap((rad) {
var collectionReference =
eventRef.where("event", isEqualTo: "festival");
return geo.collection(collectionRef: collectionReference).within(
center: center, radius: rad, field: 'position', strictMode: true);
});
}
//GEOQUERY
Widget geoQuery() {
if (currentLocationloaded && currentUserloaded) {
return Column(
children: <Widget>[
StreamBuilder(
stream: stream,
builder: (BuildContext context,
AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
if (!snapshot.hasData) {
Text("No data");
}
events =
snapshot.data.map((doc) => Event.fromDocument(doc)).toList();
events.sort((a, b) {
var aDate = a.timestamp;
var bDate = b.timestamp;
return aDate.compareTo(bDate);
});
if (events.isEmpty) {
return Text("No events");
}
return Flexible(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return buildEvent(index);
},
),
);
},
)
],
);
} else {
return circularProgress();
}
}
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: Text("Feed"),
backgroundColor: Colors.blue,
),
body: geoQuery(),
);
}
}
Update 2
If I use hard coded latitude and longitude for
GeoFirePoint center = geo.point(latitude: 37.773972, longitude: -122.431297);
it works!
Looks like an issue with passing the current user location.
Any suggestions?
The issue was that the location of current user was not passed on time.
Just put
GeoFirePoint center = geo.point(
latitude: widget.latitude,
longitude: widget
.longitude);
stream = radius.switchMap((rad) {
var collectionReference =
eventRef.where("event", isEqualTo: "festival");
return geo.collection(collectionRef: collectionReference).within(
center: center, radius: rad, field: 'position', strictMode: true);
});
from initState to geoQuery()