How to get a String from a Future<String> in Flutter? - flutter

I have a strange problem. I need the value of a Future<String> as String to display an image with CachedNetworkImage. Because I have to wait for a downloadUrl which comes from Firebase Storage I found no way to use it as normal String.
Here is my Widget where I need the value
Container(child: Obx(
() {
return CachedNetworkImage(
imageUrl: _getImage(), // Here I need the value as String
height: Get.height,
fit: BoxFit.cover);
},
))
And this is my _getImage() function
Future<String> _getImage() async {
var url = return await MyStorage().getDownloadUrl(url);
return url;
}
The getDownloadUrl() only returns a String with the download url from Firebase Storage
Future<String> getDownloadUrl(ref) async {
final StorageReference storage = FirebaseStorage().ref().child(ref);
final url = await storage.getDownloadURL();
return url.toString();
}
Because I definitely have to wait for the getDownloadUrl() I have no chance to return the value as String. But I need it as String. Otherwise I get an error.
How would you solve this problem?

A FutureBuilder will build the UI depending on the state of the Future. All you have to do is check if it has the data, then build the image.
Future<String> myFutureString() async {
await Future.delayed(Duration(seconds: 1));
return 'Hello';
}
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: myFutureString(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Container(
child: Text(snapshot.data),
);
}
return CircularProgressIndicator();
},
);
}

Related

Flutter - Instance of Future<int> returned from the function instead of the value

In the code provided the print function returns the correct number of friends that should be printed, but when I try to return it, it returns (Instance of Future ) and does not return the real value (the number)
Future<int> FriendsNumber() async {
final count = await _firestore
.collection('FriendsList')
.doc(User.userID)
.collection("FriendsList")
.where("Status", isEqualTo: 1)
.get()
.then((res) => res.size);
print('number of friends is:');
print(count);
return count;
}
return Scaffold(
body: Column(
children: [
Text(
FriendsNumber().toString();
),
],
),
);
That is how async and await works in Dart. await allows asynchronous functions to have the appearance of synchronous functions by allowing asynchronous code to be executed very similarly to synchronous code. This line in your code defers further execution of this function until the result of the firestore query is returned:
final count = await _firestore
.collection('FriendsList')
.doc(User.userID)
.collection("FriendsList")
.where("Status", isEqualTo: 1)
.get()
.then((res) => res.size);
Since dart is not blocking here it has to return some value, which in this case is a Future<int>, which bascially means that in the Future this will be resolved to an int value.
Your print statement is after the await (where the execution will pick up again when the result from firestore got returned) and thus can use value directly.
You have to use future builder for this
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FutureBuilder<int>(
future: FriendsNumber(),
builder: (BuildContext context, snapshot) {
if (!snapshot.hasData) {
// while data is loading:
return Center(
child: CircularProgressIndicator(),
);
} else {
// data loaded:
final friendNumber= snapshot.data;
return Center(
child: Text('Friends are: $friendNumber'),
);
}
},
),
),
);
}
}
You can not extract the value of a Future without using a FutureBuilder widget.
If this is a data that you will need only once and it will not change you can call your Future method inside the initState() method like that :
#override
void initState() {
super.initState();
FriendsNumber().then((value) {
setState(() {
count = value; //here you can continue your filter logic, ex: value.contains etc...
});
});
}
Doing so will allow you to display the data like you show in your post.
return Scaffold(
body: Column(
children: [
Text(
count.toString();
),
],
),
);

How to display SharedPreferences value in Text widget in flutter?

The task is to get value from SharedPrefrences and display it in the Text widget.
Here I tried to store the value in the variable _userCurrency and display it in ListTile. The problem seems that the ListTile loads quickly with the value "null".
Even getUserCurrencySymbol() isn't returning any value. I have already tried
return MySharedPreferences.instance.getUserCurrencyValue('userCurrency'); and other possible solutions.
menu.dart
late String _userCurrency;
getUserCurrencySymbol().then((value) {_userCurrency = value.toString();});
return StatefulBuilder(
builder: (BuildContext context, void Function(void Function()) setState) {
return ListView(
children: [
ListTile(title: Text(_userCurrency)),
]
) //ListView
},
); //Stateful builder
controller.dart
Future<String> getUserCurrencySymbol() async {
return MySharedPreferences.instance.getUserCurrencyValue('userCurrency').then((value) {return value.toString();});
}
class MySharedPreferences {
MySharedPreferences._privateConstructor();
static final MySharedPreferences instance = MySharedPreferences._privateConstructor();
setUserCurrencyValue(String key, String value) async {
SharedPreferences instance = await SharedPreferences.getInstance();
instance.setString(key, value);
}
getUserCurrencyValue(String key) async {
SharedPreferences instance = await SharedPreferences.getInstance();
return instance.getString(key) ?? "Bitcoin";
}
You should use setState to update the ui when the data is loaded.
getUserCurrencySymbol().then((value) {
setState((){
_userCurrency = value.toString();
});
});
You can use FutureBuilder to load data and handle loading/error states
FutureBuilder<String>(
future: getUserCurrencySymbol(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if(snapshot.hasError) {
//fixme just to check an error
print("Error: ${snapshot.error}");
return Text("Error");//or what you want
}
if (!snapshot.hasData) {
return CircularProgressIndicator();//or what you want
}
return ListView(
children: [
ListTile(title: Text(snapshot.data)),
]
);
},
)
And try to change getUserCurrencySymbol();
Future<String> getUserCurrencySymbol() {
return MySharedPreferences.instance.getUserCurrencyValue('userCurrency');
}

Why does my async method run twice in Flutter?

I want to load a static list data when entering indexScreen,but the list sometimes show twice the same content,sometimes not.
This is my list setting:List<ListClass> listItems=List<ListClass>();,ListClass is a simple class with on different attributes and a constructor.
I use home:IndexScreen() in main.dart to show Index page.
return MaterialApp(
home: IndexScreen(),
debugShowCheckedModeBanner: false,
onGenerateRoute: router.generator,
builder: EasyLoading.init(),
);
And before this page build,it will update listItems using:
Future<bool> initUserAndIndex() async{
if (curUserEmail==null) sharedGetData(USER_EMAIL).then((value) => curUserEmail=value.toString());
print(curUserEmail);
await UserTable().getUserInfo(curUserEmail).then((value){print("user ok");});
await CollectionTable().getIndexList().then((value){print("Collection ok");return true;});
return null;
}
buildPage:
#override
Widget build(BuildContext context) {
return FutureBuilder<Object>(
future: initUserAndIndex(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState==ConnectionState.waiting)
{
EasyLoading.show(status: 'loading...');
// avoid no return,this cause a whiteborad transition,I don't know how to solve it,too.
return Container();
}
else
{
EasyLoading.dismiss();
return SafeArea(
child: Scaffold(
// the listItems is used in Body()
body: Body(),
),
);
}
},
);
}
}
I run this app,and it prints twice user ok and Collection ok.But when I use ROUTER.NAVIGETE,it only prints once.
User Information is OK,but the list is such a great problem--the page shows twice content
I put my code at an order of relevance of this prblom,I think.Next I put my the two awaited funtion here:
User:
Future<bool> getUserInfo(String userEmail) async{
await userCollection.where({'userEmail':userEmail}).get().then((res) async {
//assign to the static variables
return true;
});
return null;
}
Collection:
Future<bool> getIndexList() async {
listItems.clear();
await listCollection.get().then((value){
var v = value.data;
for (var data in v) {
//get data and package them,add after the listItems list.
listItems.add(ListClass(header, content, userId, favorCount, wordCount));
}
return true;
});
}
You probably want to assign your future in your widget class, but not in the build method as the documentation show, otherwise, everytime your build method is triggered, it will call again your FutureBuilder.
final Future<String> _calculation = Future<String>.delayed(
const Duration(seconds: 2),
() => 'Data Loaded',
);
#override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
textAlign: TextAlign.center,
child: FutureBuilder<String>(
future: _calculation, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
// ...
}
),
),
}

Future<String> to String into FutureBuilder and Switch statement

in my application i have a simple method as future which that return String as Future:
Future<String> _decodeLessonUrl(BuildContext context) async {
final ContactData data = await Provider.of<ContactDao>(context).getContacts();
final String encryptedUrl =
'encoded string';
final cryptor = new PlatformStringCryptor();
try {
final String decrypted = await cryptor.decrypt(encryptedUrl, '${data.code}');
return decrypted;
} on MacMismatchException {
return null;
}
}
i want to convert this future to simple String into FutureBuilder and Switch statement:
FutureBuilder<PlayLessonResponse>(
future: _myResponse,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
PlayLessonResponse response = snapshot.data;
switch (response.status) {
case -1: // payment is ok. show play view
final Future<String> decodedLink = _decodeLessonUrl(context);// <-- problem is in this line
return PlayerWidget(link:decodedLink);
}
}
}
return Center(
child: CircularProgressIndicator( ),
);
} ),
i get this error:
error: The argument type 'Future<String>' can't be assigned to the parameter type 'String'.
in that i couldn't use then because this method should be return Widget
For converting Future<String> to String you need to use FutureBuilder.
Replace This:
final Future<String> decodedLink = _decodeLessonUrl(context);
return PlayerWidget(link:decodedLink);
With this:
return FutureBuilder<String>(
future: _decodeLessonUrl(context);
builder: (context, snapshot) {
if(snapshot.hasData){
return PlayerWidget(link:snapshot.data);
}else{
return Center(
child: CircularProgressIndicator( ),
);
}
}
);

How to use Future<bool> in Widget

I have a Future function in my Provider Repository. However it is Future<bool> since I need async for http request.
Future<bool> hasCard() async {
String token = await getToken();
var body = jsonEncode({"token": token, "userID": user.getUserID()});
var res = await http.post((baseUrl + "/hasCard"), body: body, headers: {
"Accept": "application/json",
"content-type": "application/json"
});
print(res.toString());
if (res.statusCode == 200) {
this.paymentModel = PaymentModel.fromJson(json.decode(res.body));
return true;
}
return false;
}
And in my Widget I want to check this value:
Widget build(BuildContext context) {
var user = Provider.of<UserRepository>(context);
if(user.hasCard())
{
//do something
}
But I get an error message:
Conditions must have a static type of 'bool'.dart(non_bool_condition)
Since it is a Widget type I cannot use async here. What could be the way to solve this?
You can use a FutureBuilder, it will build the widget according to the future value, which will be null until the future is completed. Here is an example.
FutureBuilder(
future: hasCard(),
builder: (context, snapshot) {
if (snapshot.data == null)
return Container(
width: 20,
height: 20,
child: CircularProgressIndicator());
if (snapshot.data)
return Icon(
Icons.check,
color: Colors.green,
);
else
return Icon(
Icons.cancel,
color: Colors.red,
);
},
)
Well not only for a Future<bool> for any other future you can use the FutureBuilder where the future is what returns you the type of future and snapshot is the data recieved from it.
FutureBuilder(
future: hasCard(),
builder: (context, snapshot) {
if (snapshot.data != null){
print(snapshot.data)}
else{
print("returned data is null!")}
},
)
and I would suggest assigning a default value to your bool.
good luck!