ternary operator is not working as expected in flutter - flutter

I have to render screens condition wise. but conditions are not getting fulfilled.
My function is always returns the false boolean value even if it doesn't have to.
Here is my code
class _HomePageState extends State<HomePage> {
Future<bool> checkIsLoggedIn() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var data = await prefs.getString('isAuthenticated');
// the value of data is true here
print(data);
if (data == "true") {
return true;
} else {
return false;
}
}
#override
void initState() {
super.initState();
checkIsLoggedIn();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: checkIsLoggedIn() == true ? Profile() : LoginScreen());
//error : checkIsLoggedin () function returns false in my case.
}
}

Use then() to wait for the Future. Then, assign the result with setState().
class _HomePageState extends State<HomePage> {
Future<bool> checkIsLoggedIn() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var data = await prefs.getString('isAuthenticated');
// the value of data is true here
print(data);
if (data == "true") {
return true;
} else {
return false;
}
}
var isLoggedIn = false; // HERE
#override
void initState() {
super.initState();
checkIsLoggedIn().then((result) { // HERE
setState(() {
isLoggedIn = result;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: isLoggedIn == true // HERE
? Profile()
: LoginScreen());
}
}

When getting into problems, I do recommend you are first looking into the warnings you are getting around the problematic area. If you are using a default Flutter project, you should have the lint unrelated_type_equality_checks activated since it is part of the Core set of Dart Lints:
DON'T Compare references of unrelated types for equality.
Comparing references of a type where neither is a subtype of the other most likely will return false and might not reflect programmer's intent.
https://dart-lang.github.io/linter/lints/unrelated_type_equality_checks.html
This would give the following warning in your IDE with a link to the documentation I linked to previously:
The problem is that your checkIsLoggedIn() method have the following signature which means it returns an object of the type Future<bool>:
Future<bool> checkIsLoggedIn() async {
And you are then comparing this against true which are of the type bool. If you are doing an equality check against these two different types, it will ALWAYS end up returning false, which is also what you are observing.
A Future also means that you are handling a value that are potentially still not created. So when you are running your checkIsLoggedIn() == true it does not even know yet if the bool inside your Future<bool> are true or false.
The solution are to either make it so checkIsLoggedIn() returns bool, which cannot be done in this case since the method are handling asynchronously logic.
Another solution would then be to await the returned Future from checkIsLoggedIn(). But we cannot use await unless we mark the method, we are inside, as async. But for Flutter, we are not allowed to have build() marked as async.
So a solution would instead be to handle the asynchronously nature of checkIsLoggedIn() by using FutureBuilder:
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Which are a Widget made for handling the building of the GUI when we don't know the value of the Future, but then triggers an automatic rebuild when the value of the Future are received.

You can use .getbool instead .getString
To wait for checkIsLoggedIn result you can use a FutureBuilder.
Here an example :D
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
Future<bool> checkIsLoggedIn() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final isAuthenticated = await prefs.getBool('isAuthenticated');
return isAuthenticated;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<bool>(
future: checkIsLoggedIn(),
builder: (context, snapshot) {
// SHOW LOADING WHILE PREFS IS GETTING DATA
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
// SHOW SCREEN ACCORDING DATA
return snapshot.data! //
? const Profile()
: const LoginScreen();
},
),
);
}
}

Related

Undefined name 'mounted'

I am using provider for the state management on Flutter. I'm making async await function and have warning that Do not use BuildContexts across async gaps. So I tried to put 'if(!mounted)' code and I got warning that Undefined name 'mounted'.
How can I fix this problem? Thank you!
Provider codes
signIn(BuildContext context) async{
try {
final navigator = Navigator.of(context);
!isSignupValid ? isSignupValid = true : null;
await authentication.signInWithEmailAndPassword(
email: userEmail.trim(), password: userPassword.trim()
);
navigator.pop();
} on FirebaseAuthException catch (errorCode) {
isSignupValid = false;
print('isSignupValid : $isSignupValid');
print('SignIn FirebaseAuthException : $errorCode');
ScaffoldMessenger.of(context).showSnackBar(
returnSnackBar(context, errorCode)
);
}
await Future.delayed(const Duration(seconds: 0));
if (!mounted) return;
context.watch<ProfileData>().profileImage = null;
notifyListeners();
}
The mounted property is only available in a StatefulWidget. If you are not in a stateful widget you have no way of knowing whether the context you are using still references the state of a widget which is still in the widget tree.
I'm not sure exactly what you do. You can either change your widget to a StatefulWidget or simply do a final profileData = context.read<ProfileData>() at the beginning of you method, and never access context after your first async call.
If you want to check whether the widget is mounted in a StatelessWidget, you can try using a GlobalKey to obtain a reference to the widget and then check its mounted property:
class MyStatelessWidget extends StatelessWidget {
final _globalKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Container(
key: _globalKey,
child: Text('Hello, world!'),
);
}
bool get isMounted => _globalKey.currentState != null && _globalKey.currentState!.mounted;
}

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

Async Data Initialization in initState

I'm calling an async method getMyLocation() to get my current location in my initState(). The method can take a while...
I wanted to understand the behavior of initState() in these cases. Does the method still execute in the background as build() renders or does initState() timeout since it needs to complete before build() renders?
In my build() I have a statement checking if my latitude is null, in which case I return a Loading() widget. Sometimes Screen() renders and sometimes Loading() goes on indefinitely. I am assuming sometimes the getMyLocation() successfully executes during initState() and sometimes it timesout?
#override
void initState() {
super.initState();
final userData = Provider.of<MyUser>(context, listen: false);
final myUser = userData.getUser();
userData.getMyLocation();
}
getMyLocation() async {
_myUser.longitude = await getCurrentLongitude();
_myUser.latitute = await getCurrentLatitude();
notifyListeners();
}
Widget build(BuildContext context) {
final userData = Provider.of<MyUser>(context);
final myUser = userData.getUser();
myUser.latitude == null?
return Loading()
: return Screen()
Great question. First of all, initState() runs synchronously, it prepares various things needed for build() method to run properly. If you are executing some async function here, it will just return a Future because you can't await it in the initState(). In your case you probably need a FutureBuilder. The "proper way" of dealing with futures would be something like:
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Future<void> getMyLocation() async {
final userData = Provider.of<MyUser>(context, listen: false);
final myUser = await userData.getUser();
// if getUser() is async then we have to await
myUser.longitude = await getCurrentLongitude();
myUser.latitute = await getCurrentLatitude();
// notifyListeners();
// You probably do not need this, should be done in provider methods instead
}
Widget build(BuildContext context) {
return FutureBuilder(
future: getMyLocation(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return SomeErrorWidget();
}
if (snapshot.hasData) {
return Screen(snapshot.data);
}
return SomeLoadingWidget();
});
}

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

How to reference variable in method in FutureBuilder (builder:)?

I want to use the variable dbRef in inputData() in future Builder builder: you can see the variable in between asterisk .
void inputData() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
final uid = user.uid;
final **dbRef** = FirebaseDatabase.instance.reference().child("Add Job Details").child(uid).child("Favorites");
}
#override
Widget build(BuildContext context) {
return FutureBuilder (
future: **dbRef**.once(),
builder: (context, AsyncSnapshot<DataSnapshot> snapshot) {
if (snapshot.hasData) {
List<Map<dynamic, dynamic>> list = [];
for (String key in snapshot.data.value.keys) {
list.add(snapshot.data.value[key]);
}
This is one more approach to tackle the problem.
The idea is to use a variable _loading and set it to true initially.
Now, after in your inputData() function, you can set it to false once you get the dbref.
Store dbref, the way I stored _myFuture in the code below i.e., globally within the class.
Use your _loading variable to return a progress bar if its true else return FutureBuilder with your dbref.once() in place. Now, that you have loaded it, it should be available at this point.
class MyWidget extends StatefulWidget {
#override
createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
// Is the future being loaded?
bool _loading;
// This is the future we will be using in our FutureBuilder.
// It is currently null and we will assign it in _loadMyFuture function.
// Until assigned, we will keep the _loading variable as true.
Future<String> _myFuture;
// Load the _myFuture with the future we are going to use in FutureBuilder
Future<void> _loadMyFuture() async {
// Fake the wait for 2 seconds
await Future.delayed(const Duration(seconds: 2));
// Our fake future that will take 2 seconds to return "Hello"
_myFuture = Future(() async {
await Future.delayed(const Duration(seconds: 2));
return "Hello";
});
}
// We initialize stuff here. Remember, initState is called once in the beginning so hot-reload wont make flutter call it again
#override
initState() {
super.initState();
_loading = true; // Start loading
_loadMyFuture().then((x) => setState(() => _loading = false)); // Set loading = false when the future is loaded
}
#override
Widget build(BuildContext context) {
// If loading, show loading bar
return _loading?_loader():FutureBuilder<String>(
future: _myFuture,
builder: (context, snapshot) {
if(!snapshot.hasData) return _loader(); // still loading but now it's due to the delay in _myFuture
else return Text(snapshot.data);
},
);
}
// A simple loading widget
Widget _loader() {
return Container(
child: CircularProgressIndicator(),
width: 30,
height: 30
);
}
}
Here is the output of this approach
This does the job but, you might need to do it for every class where you require your uid.
========================================
Here is the approach I described in the comments.
// Create a User Manager like this
class UserManager {
static String _uid;
static String get uid => _uid;
static Future<void> loadUID() async {
// Your loading code
await Future.delayed(const Duration(seconds: 5));
_uid = '1234'; // Let's assign it directly for the sake of this example
}
}
In your welcome screen:
class MyWidget extends StatefulWidget {
#override
createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
bool _loading = true;
#override
void initState() {
super.initState();
UserManager.loadUID().then((x) => setState(() => _loading = false));
}
#override
Widget build(BuildContext context) {
return _loading ? _loader() : Text('Welcome User ${UserManager.uid}!');
}
// A simple loading widget
Widget _loader() {
return Container(child: CircularProgressIndicator(), width: 30, height: 30);
}
}
The advantage of this method is that once you have loaded the uid, You can directly access it like this:
String uid = UserManager.uid;
thus eliminating use of futures.
Hope this helps!