getting the return value of Future Function in Flutter without Stateless/Stateful Widget - flutter

I just want to return the value of each condition and use it as the value of the function.
Here is my code for it.
var getStuff = chechIfExisting();
chechIfExisting() async {
var isExisting = await FirebaseFirestore.instance
.collection('merchants')
.doc(userUid)
.get();
if (isExisting.exists) {
return 'exists';
}
if (!isExisting.exists) {
return 'nope';
} else {
return 'error';
}
}
and I am not using any Stateless and Stateful Widget since this file contains only the widgets such as appbar/mydrawer. I wanted to use the 'getStuff' variable in a if statement under the myDrawer Widget, since I want to dynamically check if the the data that I am fetching is existing or not.
myDrawer(BuildContext context) {
print('getStuff');
// only prints 'Instance of 'Future<String>' and does not return the value.
}
I want to be able to use the 'getStuff' variable as
myDrawer(BuildContext context) {
if(getStuff == 'exists'){
// code here
}
Any helps/ideas on how to solve this are appreciated!

with this line:
var getStuff = chechIfExisting();
You're not waiting for the method to finishes executing, since it's a Future, not using either await/async or then to resolve values after the Future finishes will get you that getStuff() is a type of Instance of 'Future<String>, before the running of the myDrawer function, you need either to:
var getStuff = await chechIfExisting(); // inside an async method
or:
chechIfExisting().then((resolvedValue) {
getStuff = resolvedValue;
});
then you can run the myDrawer method and get it working fine

Related

Flutter RiverPod: Is it ok to return another provider from build method of Notifier?

I want to keep my return value as AsyncValue rather than Stream so I am returning StreamProvider from build method of Notifier. After reading the codebase of riverpod I can't see any drawback of this, but I have never come across any project doing something like this. Is this fine, or is there any straight forward way to convert Stream to AsyncValue.
final _userProvider = StreamProvider.autoDispose<User?>((ref) {
final repository = ref.watch(repositoryProvider);
return repository.getUser(); //returns Stream<User?>
});
class AuthNotifier extends AutoDisposeNotifier<AsyncValue<User?>> {
#override
AsyncValue<User?> build() {
return ref.watch(_userProvider);
}
Future<void> singOut() {
return ref.read(repositoryProvider).signOut();
}
}
final authProvider =
AutoDisposeNotifierProvider<AuthNotifier, AsyncValue<User?>>(
AuthNotifier.new);
This is fine, yes.
Being able to do such a thing is the goal of the build method & ref.watch
As long as you don't return the provider itself but the value exposed by the provider, there is no problem:
build() {
return ref.watch(provider); // OK
}
build() {
return provider // KO
}

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

How can I access json data as variable inside my scaffold in flutter?

How can I access var apiData = jsonDecode(response.body);
inside Widget build(BuildContext context) => Scaffold()
I want to use something like
if (apiData["studentEmail"] == "") { return const SignIn(); } else { return const Dashboard(); }
First of all, I suggest that you do the service operations in a separate class. I suggest you look at the service-repository pattern for this.
Bring your data from the api by creating the getApi method inside the service class.
For example,
class MyApi{
final String _getApi =
"https://free.currconv.com/api/v7/convert?q=USD_TRY,EUR_TRY&compact=ultra&apiKey=26cb9ffd85f9bee9c208";
Future<StudentModel?> getDatas() async {
var response = await Dio().get(_getApi);
if (response.statusCode == 200) {
return StudentModel.fromJson(response.data);
} else {
debugPrint('${response.statusCode} : ${response.data.toString()}');
throw UnimplementedError();
}
}
}
After that, Using FutureBuilder, give the future property the method that brings the api to your api class. And now you can access your data with the builder's AsynSnapshot. You can easily access the data in the future method with the snapshot that FutureBuilder now gives you.
FutureBuilder<StudentModel>(
future: MyApi.getDatas,
builder: (context, AsynSnapshot asynSnapshot){
// You can easily access the data in the future method with the
// snapshot that FutureBuilder now gives you.
asynSnapshot.data.yourData;
}
)

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

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');
}
}

where to load model from file in flutter apps?

Suppose I store my data in a dedicated repo class like so:
class UrlEntry {
final String url;
final String title;
UrlEntry({#required this.url, this.title});
}
class UrlRepository with ChangeNotifier {
List<UrlEntry> urlEntries = new List<UrlEntry>();
// Returns the urls as a separate list. Modifyable, but doesnt change state.
List<UrlEntry> getUrls() => new List<UrlEntry>.from(urlEntries);
add(UrlEntry url) {
this.urlEntries.add(url);
print(
"url entry ${url.url} added. Now having ${urlEntries.length} entries ");
notifyListeners();
}
removeByUrl(String url) {
var beforeCount = this.urlEntries.length;
this.urlEntries.removeWhere((entry) => entry.url == url);
var afterCount = this.urlEntries.length;
if (beforeCount != afterCount) notifyListeners();
print("removed: ${beforeCount != afterCount}");
}
save() async {
final storageFile = await composeStorageFile();
print("storage file is '${storageFile.path}");
if (await storageFile.exists()) {
print("deleting existing file");
await storageFile.delete();
}
if (urlEntries == null || urlEntries.length < 1) {
print("no entries to save");
return false;
}
print(
"saving ${urlEntries.length} url entries to file $storageFile} ...");
for (var entry in urlEntries) {
await storageFile.writeAsString('${entry.url} ${entry.title}',
mode: FileMode.append);
}
}
Future<File> composeStorageFile() async {
Directory storageDir = await getApplicationDocumentsDirectory();
return File('${storageDir.path}/url_collection.lst');
}
void dispose() async {
super.dispose();
print("disposing ...");
urlEntries.clear();
this.urlEntries = null;
}
load() async {
final storageFile = await composeStorageFile();
if (!await storageFile.exists()) {
print("storage file ${storageFile.path} not existing - not loading");
return false;
}
print("loading file ${storageFile.path}");
urlEntries = List <UrlEntry> () ;
final fileLines = storageFile.readAsLinesSync() ;
for (var line in fileLines) {
var separatorIndex = line.indexOf(' ') ;
final url = line.substring(0, separatorIndex) ;
var title = line.substring(separatorIndex+1) ;
if (title == 'null') title = null ;
urlEntries.add(new UrlEntry(url: url, title: title)) ;
}
notifyListeners() ;
}
}
Above code has several issues I unfortunately donnot know how to circumvent:
most of the methods of UrlRepository are async. This is because of getApplicationDocumentsDirectory() being async. I think former is an absolute flaw but introducing semaphores here to create an artificial bottleneck would pollute the code, so I still stick to async; but call me old-fashioned - I dont like the idea having save and load operations being theoretically able to overlap each other. I mean, with getApplicationDocumentsDirectory, we're talking about a simple configurational detail that will not need much computational power to compute, nor to store, nor will it change that often and it pollutes the code with otherwise unnessecary stuff. So, Is there another way to get the results of getApplicationDocumentsDirectory() without await / async / then ?
If this is not the case - where should I put the call to save()? My first idea was to save data not every model change, but instead at the latest possible executional place, which is one of the dispose-related methods, like so:
class MyAppState extends State<MyApp> {
UrlRepository urlRepository;
...
#override
void deactivate() async {
await urlRepository.save() ;
super.deactivate();
}
Unfortunately this results in urlRepository.save() being executed only the half, no matter whether I call it in a unit test, on a avd or on a real device. Right in the middle its terminated - I checked that with printouts. I think this is because, being forced again to make a completely unrelated method async (here deactivate()), I have to accept that execution is not granted to terminate at the return command, but earlier (?). I tried to put the call to MyState.dispose() as well as to urlRepository.dispose() with the same result except I cannot make the dispose methods async and hence just call save() async and hope everything has been saved before super.dispose() kicks in,...
I thought it natural to load the repositotry state inside of initState(), but I want to make sure that either the load has completed before creating widgets (ie calling the builder), or will be loaded after all widgets have already been in place so the model change will trigger rebuild. Since load() has to be async for known reasons and initState is not, I cannot assure even one of above cases and stick with urlRepository.load() and hope the best. So, where to put the call to urlRepository.load() ?
First: You have to use async/await methods because you don't know what the user's device may be doing while running your application, so even though the device might be performing a process that "doesn't need much power computational" that process may take a little longer than expected.
Second: Do not trust in deactivate() and dispose() functions, the user could kill your app and it would never do the save process. I'm not really sure how to automate this process. I suggest you do it manually somewhere in your code.
Third: Don't use initState() to load your data, you should use a FutureBuilder in which the future parameter is your urlRepository.load() function, while its loading you can show a CircularProgressIndicator() and if it has data you show your widget.
Example:
#override
Widget build() {
return FutureBuilder(
future: urlRepository.load() // without await keyword
builder: (context, snapshot) {
if(!snapshot.hasData)
return CircularProgressIndicator();
return YourWidget(); // if snapshot.hasData is true the process has finished
}
);
}
Psdt: It might be useful if urlRepository.load() would return something like a bool. Doing this you could show a widget if snapshot.data is true or another widget if snapshot.data is false.