How to get the 'bool' value from a Future<bool> into a field variable, for later use - flutter

I am using flutter_blue package for using the Bluetooth service. I want to check whether the device has Bluetooth capabilities. The method isAvailable seems to do it. However, it returns a Future<bool>, which I am tryting to get into a variable as follows:
import 'package:flutter_blue/flutter_blue.dart';
class BT_Base {
final FlutterBlue _fb = FlutterBlue.instance;
bool BTAvailable = true; // as a default placeholder
BT_Base () {
BTAvailable = _fixAvail();
}
_fixAvail () async {
return await _fb.isAvailable;
}
...
I try to get the future value from it and store into BTAvailable. Later on, I use the fixed BTAvailable field to get the appropriate Widget to be passed onto as follows:
class BTDevicePrompt extends StatelessWidget {
#override
Widget build(BuildContext context) {
BT_Base bt = BT_Base();
var btDeviceRes = bt.scan();
if(!bt.BTAvailable) return Text('Bluetooth unavailable on device...');
else if (btDeviceRes.isEmpty) return Text('No Bluetooth devices in range...');
else {
return CupertinoActionSheet(
actions: [
...
],
)
}
}
}
But I keep getting the error type 'Future<dynamic>' is not a subtype of type 'bool' at runtime. How can I use the Future properly in this situation? It is alright if the whole process just halts and waits for this part as well.
I have gone through a lot of solutions but I am not able to piece it together.

Any method marked async always returns a Future of some kind. You can give it an explicit return type like Future<bool> function() async { ... }, or if you leave it out it will infer Future<dynamic>.
In short, you can't get a bool from a Future<bool> outside of an async function (there are technically ways but almost certainly not what you want in Flutter).
This makes sense, since the whole point of a Future<bool> is that it's going to be a bool in the future. If there was some process to convert from a Future<bool> to a bool, what should it do if the future has not yet completed? Perhaps it should wait until it has completed. In that case, you're just describing the await keyword.
If, however, you want to use a Future in your UI in a Flutter application, you have a few options.
The simplest for your case will be to move it into initState():
class BTDevicePrompt extends StatefulWidget {
// stateful widget boilerplate
}
class BTDevicePromptState extends State<BTDevicePrompt> {
bool isAvailable = false;
#override
void initState() {
super.initState();
checkAvailable(); // use a helper method because initState() cannot be async
}
Future<void> checkAvailable() async {
// call an async function and wait for it to complete
bool result = await checkIfBluetoothAvailable();
setState(() => bluetoothAvailable = result); // set the local variable
}
#override
Widget build(BuildContext context) {
if (bluetoothAvailable) return Text('bluetooth available');
else return Text('bluetooth not available');
}
}

Related

ternary operator is not working as expected in 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();
},
),
);
}
}

Flutter jsonDecode FlutterSession value is not loading in widget initially. but works on hotload

i am initializing a variable with value from session. but could not print it in the widget. but it is showing after hot load. here is my code :
class _dashboardState extends State<dashboard> {
var logindata;
#override
initState() {
super.initState();
_getSession() async {
logindata = jsonDecode(await FlutterSession().get("login_data"));
}
_getSession();
}
#override
Widget build(BuildContext context) {
print(logindata); // prints null
}
}
Instead of jsonDecode(await FlutterSession().get("login_data"))
if i add any random string or number like
logindata = "Session value";
it prints normally. otherwise on hot load
only i am getting the session value.
what will be the reason?
please do help :(. i am new to flutter.
After following ideas from the comments i have updated the code as follows:
class _dashboardState extends State<dashboard> {
var logindata;
#override
void initState() {
getSessionValue().then((logindata) {
setState(() {
logindata = logindata;
});
});
super.initState();
}
Future<void> getSessionValue() async {
logindata = jsonDecode(await FlutterSession().get("login_data"));
return logindata;
}
#override
Widget build(BuildContext context) {
print(logindata); // first prints null then correct array without hotload.
}
}
here i got first null, then the correct value. but in my case i need the value of an object in the array logindata, that is
logindata["shop_name"] . so in that case i am getting error The method '[]' was called on null. Receiver: null Tried calling: []("shop_name") . What do i do now ? i am really stuck here. :(
Let me explain this first,
lifecycle of State goes like this createState -> initState ->........-> build
so you're right about the order of execution
you're calling getSessionValue() from initState and expecting widget to build right after it, but since getSessionValue() returns a Future after awaiting,
the execution continues and builds the widget not waiting for the returned Future value from getSessionValue(), so it prints null initially, and then when the Future is available youre calling setState and it prints the actual value
there is no notable delay here but the execution flow causes it to behave like this
so what's the solution?... Here comes FutureBuilder to the rescue
it is possible to get different states of a Future using FutureBuilder and you can make changes in the UI accordingly
so in your case, inside build, you can add a FutureBuilder like this,
FutureBuilder(
future: getSessionValue(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none: return Text("none");
case ConnectionState.waiting: return Text("waiting");
case ConnectionState.active: return Text("active");
case ConnectionState.done:
print(logindata); // this will print your result
return Text("${logindata}");
}
})
keep in mind that the builder should always return a widget
as the async operation is running, you can show the progress to the user by
showing the appropriate UI for different states
for eg: when in ConnectionState.waiting, you can show/return a progress bar
Hope this helps, Thank you
That is a normal behaviour since you are having an async function to get the login data (so it will take some time to be there) while the widget will be building , then build method will get executed first which will make the print executed with no data and then when you hot reload it will be executed perfectly , so if you you want to print it right after you get the data you can make the function this way :
_getSession() async {
logindata = jsonDecode(await FlutterSession().get("login_data")).then((value) {print(value);}); }

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

Flutter: setState() does not trigger a build

I have a very simple (stateful) widget that contains a Text widget that displays the length of a list which is a member variable of the widget's state.
Inside the initState() method, I override the list variable (formerly being null) with a list that has four elements using setState(). However, the Text widget still shows "0".
The prints I added imply that a rebuild of the widget has not been triggered although my perception was that this is the sole purpose of the setState() method.
Here ist the code:
import 'package:flutter/material.dart';
class Scan extends StatefulWidget {
#override
_ScanState createState() => _ScanState();
}
class _ScanState extends State<Scan> {
List<int> numbers;
#override
void initState() {
super.initState();
_initializeController();
}
#override
Widget build(BuildContext context) {
print('Build was scheduled');
return Center(
child: Text(
numbers == null ? '0' : numbers.length.toString()
)
);
}
Future<List<int>> _getAsyncNumberList() {
return Future.delayed(Duration(seconds: 5), () => [1, 2, 3, 4]);
}
_initializeController() async {
List<int> newNumbersList = await _getAsyncNumberList();
print("Number list was updated to list of length ${newNumbersList.length}");
setState(() {
numbers = newNumbersList;
});
}
}
My question: why does the widget only build once? I would have expected to have at least two builds, the second one being triggered by the execution of setState().
I have the feeling, the answers don't address my question. My question was why the widget only builds once and why setState() does not trigger a second build.
Answers like "use a FutureBuilder" are not helpful since they completely bypass the question about setState(). So no matter how late the async function finishes, triggering a rebuild should update the UI with the new list when setState() is executed.
Also, the async function does not finish too early (before build has finished). I made sure it does not by trying WidgetsBinding.instance.addPostFrameCallback which changed: nothing.
I figured out that the problem was somewhere else. In my main() function the first two lines were:
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp,DeviceOrientation.portraitDown]
);
which somehow affected the build order. But only on my Huawei P20 Lite, on no other of my test devices, not in the emulator and not on Dartpad.
So conclusion:
Code is fine. My understanding of setState() is also fine. I haven't provided enough context for you to reproduce the error. And my solution was to make the first two lines in the main() function async:
void main() async {
await SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
await SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp,DeviceOrientation.portraitDown]
);
...
}
I don't know why you say your code is not working, but here you can see that even the prints perform as they should. Your example might be oversimplified. If you add a delay to that Future (which is a real case scenario, cause fetching data and waiting for it does take a few seconds sometimes), then the code does indeed display 0.
The reason why your code works right now is that the Future returns the list instantly before the build method starts rendering Widgets. That's why the first thing that shows up on the screen is 4.
If you add that .delayed() to the Future, then it does indeed stop working, because the list of numbers is retrieved after some time and the build renders before the numbers are updated.
Problem explanation
SetState in your code is not called properly. You either do it like this (which in this case makes no sense because you use "await", but generally it works too)
_initializeController() async {
setState(() {
List<int> newNumbersList = await _getAsyncNumberList();
print("Number list was updated to list of length ${newNumbersList.length}");
numbers = newNumbersList;
});
}
or like this
_initializeController() async {
List<int> newNumbersList = await _getAsyncNumberList();
print("Number list was updated to list of length ${newNumbersList.length}");
numbers = newNumbersList;
setState(() {
/// this thing right here is an entire function. You MUST HAVE THE AWAIT in
/// the same function as the update, otherwise, the await is callledn, and on
/// another thread, the other functions are executed. In your case, this one
/// too. This one finishes early and updates nothing, and the await finishes later.
});
}
Suggested solution
This will display 0 while waiting 5 seconds for the Future to return the new list with the data and then it will display 4. If you want to display something else while waiting for the data, please use a FutureBuilder Widget.
FULL CODE WITHOUT FutureBuilder:
class Scan extends StatefulWidget {
#override
_ScanState createState() => _ScanState();
}
class _ScanState extends State<Scan> {
List<int> numbers;
#override
void initState() {
super.initState();
_initializeController();
}
#override
Widget build(BuildContext context) {
print('Build was scheduled');
return Center(
child: Text(numbers == null ? '0' : numbers.length.toString()));
}
Future<List<int>> _getAsyncNumberList() {
return Future.delayed(Duration(seconds: 5), () => [1, 2, 3, 4]);
}
_initializeController() async {
List<int> newNumbersList = await _getAsyncNumberList();
print(
"Number list was updated to list of length ${newNumbersList.length}");
numbers = newNumbersList;
setState(() {});
}
}
I strongly recommend using this version, since it displays something to the user the whole time while waiting for the data and also has a failsafe if an error comes up. Try them out and pick what is best for you, but again, I recommend this one.
FULL CODE WITH FutureBuilder:
class _ScanState extends State<Scan> {
List<int> numbers;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
print('Build was scheduled');
return FutureBuilder(
future: _getAsyncNumberList(),
builder: (BuildContext context, AsyncSnapshot<List<int>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return Center(child: Text('Fetching numbers...'));
default:
if (snapshot.hasError)
return Center(child: Text('Error: ${snapshot.error}'));
else
/// snapshot.data is the result that the async function returns
return Center(child: Text('Result: ${snapshot.data.length}'));
}
},
);
}
Future<List<int>> _getAsyncNumberList() {
return Future.delayed(Duration(seconds: 5), () => [1, 2, 3, 4]);
}
}
Here is a more detailed example with a full explanation of how FutureBuilder works. Take some time and carefully read through it. It's a very powerful thing Flutter offers.

Flutter provider in initState

I'm currently trying Provider as a state management solution, and I understand that it can't be used inside the initState function.
All examples that I've seen call a method inside a derived ChangeNotifier class upon user action (user clicks a button, for example), but what if I need to call a method when initialising my state?
Motivation:
Creating a screen which loads assets (async) and shows progress
An example for the ChangeNotifier class (can't call add from initState):
import 'package:flutter/foundation.dart';
class ProgressData extends ChangeNotifier {
double _progress = 0;
double get progress => _progress;
void add(double dProgress) {
_progress += dProgress;
notifyListeners();
}
}
You can call such methods from the constructor of your ChangeNotifier:
class MyNotifier with ChangeNotifier {
MyNotifier() {
someMethod();
}
void someMethod() {
// TODO: do something
}
}
Change your code to this
class ProgressData extends ChangeNotifier {
double _progress = 0;
double get progress => _progress;
void add(double dProgress) async {
// Loading Assets maybe async process with its network call, etc.
_progress += dProgress;
notifyListeners();
}
ProgressData() {
add();
}
}
In initState all the of(context) things don't work correctly, because the widget is not fully wired up with every thing in initState.
You can use this code:
Provider.of<ProgressData>(context, listen: false).add(progress)
Or this code:
Future.delayed(Duration.zero).then(_){
Provider.of<ProgressData>(context).add(progress)
}):
So an AssetLoader class which reports on its progress will look something like this, I guess:
import 'package:flutter/foundation.dart';
class ProgressData extends ChangeNotifier {
double _progress = 0;
ProgressData() {
_loadFake();
}
Future<void> _loadFake() async {
await _delayed(true, Duration(seconds: 1));
_add(1.0);
await _delayed(true, Duration(seconds: 2));
_add(2.0);
await _delayed(true, Duration(seconds: 3));
_add(3.0);
}
// progress
double get progress => _progress;
// add
void _add(double dProgress) {
_progress += dProgress;
notifyListeners();
}
// _delayed
Future<dynamic> _delayed(dynamic returnVal, Duration duration) {
return Future.delayed(duration, () => returnVal);
}
}
As Fateme said:
the widget is not fully wired up with everything in initState
Also, you can use something like this in your initState
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
Provider.of<ProgressData>(context, listen: false).add(5);
});
I think it's more standard!
Be aware that you should use the correct context! I mean the context of the Builder!
The problem here lies with the fact that context does not exist yet in initState as extensively explained by the other answers. It doesn't exist because it hasn't yet been made a part of the widget tree.
Calling a method
If you're not assigning any state and only calling a method then initState would be the best place to get this done.
// The key here is the listen: false
Provider.of<MyProvider>(context, listen: false).mymethod();
The code above is allowed by Flutter because it doesn't have to listen for anything. In short, it's a one off. Use it where you only want to do something instead of read/listen to something.
Listening to changes
Alternatively, if you need to listen to changes from Provider then the use of didChangeDependencies would be the best place to do so as context would exist here as in the docs.
This method is also called immediately after initState.
int? myState;
#override
void didChangeDependencies() {
// No listen: false
myState = Provider.of<MyProvider>(context).data;
super.didChangeDependencies();
}
If you've never used didChangeDependencies before, what it does is get called whenever updateShouldNotify() returns true. This in turn lets any widgets that requested an inherited widget in build() respond as needed.
I'd usually use this method in a FutureBuilder to prevent reloading data when data already exists in Provider after switching screens. This way I can just check Provider for myState and skip the preloader (if any) entirely.
Hope this helps.