I'm trying to better understand Futures in Flutter. In this example, my app makes an API call to get some information of type Future<String>
. I'd like to display this information in a Text()
widget. However, because my String
is wrapped in a Future
I'm unable to put this information in my Text()
widget, and I'm not sure how to handle this without resorting to a FutureBuilder
to create the small widget tree.
The following example uses a FutureBuilder
and it works fine. Note that I've commented out the following line near the bottom:
Future<String> category = getData();
Is it possible to turn category
into a String
and simply drop this in my Text()
widget?
import 'package:flutter/material.dart';
import 'cocktails.dart';
class CocktailScreen extends StatefulWidget {
const CocktailScreen({super.key});
@override
State<CocktailScreen> createState() => _CocktailScreenState();
}
class _CocktailScreenState extends State<CocktailScreen> {
@override
Widget build(BuildContext context) {
Cocktails cocktails = Cocktails();
Future<String> getData() async {
var data = await cocktails.getCocktailByName('margarita');
String category = data['drinks'][0]['strCategory'];
print('Category: ${data["drinks"][0]["strCategory"]}');
return category;
}
FutureBuilder categoryText = FutureBuilder(
initialData: '',
future: getData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
return Text(snapshot.data);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
}
return const CircularProgressIndicator();
},
);
//Future<String> category = getData();
return Center(
child: categoryText,
);
}
}
Here's my Cocktails
class:
import 'networking.dart';
const apiKey = '1';
const apiUrl = 'https://www.thecocktaildb.com/api/json/v1/1/search.php';
class Cocktails {
Future<dynamic> getCocktailByName(String cocktailName) async {
NetworkHelper networkHelper =
NetworkHelper('$apiUrl?s=$cocktailName&apikey=$apiKey');
dynamic cocktailData = await networkHelper.getData();
return cocktailData;
}
}
And here's my NetworkHelper
class:
import 'package:http/http.dart' as http;
import 'dart:convert';
class NetworkHelper {
NetworkHelper(this.url);
final String url;
Future<dynamic> getData() async {
http.Response response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
String data = response.body;
var decodedData = jsonDecode(data);
return decodedData;
} else {
//print('Error: ${response.statusCode}');
throw 'Sorry, there\'s a problem with the request';
}
}
}
CodePudding user response:
The best way is using FutureBuilder
:
FutureBuilder categoryText = FutureBuilder<String>(
future: getData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
var data = snapshot.data ?? '';
return Text(data);
}
}
},
),
but if you don't want to use FutureBuilder
, first define a string variable like below and change your adasd to this :
String category = '';
Future<void> getData() async {
var data = await cocktails.getCocktailByName('margarita');
setState(() {
category = data['drinks'][0]['strCategory'];
});
}
then call it in initState :
@override
void initState() {
// TODO: implement initState
super.initState();
}
and use it like this:
@override
Widget build(BuildContext context) {
return Center(
child: Text(category),
);
}
remember define category
and getData
and cocktails
out of build method not inside it.
CodePudding user response:
Yes, you can achieve getting Future
value and update the state based on in without using Using FutureBuilder
, by calling the Future
in the initState()
, and using the then
keyword, to update the state when the Future returns a snapshot.
class StatefuleWidget extends StatefulWidget {
const StatefuleWidget({super.key});
@override
State<StatefuleWidget> createState() => _StatefuleWidgetState();
}
class _StatefuleWidgetState extends State<StatefuleWidget> {
String? text;
Future<String> getData() async {
var data = await cocktails.getCocktailByName('margarita');
String category = data['drinks'][0]['strCategory'];
print('Category: ${data["drinks"][0]["strCategory"]}');
return category;
}
@override
void initState() {
getData().then((value) {
setState(() {
text = value;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Text(text ?? 'Loading');
}
}
here I made the text
variable nullable, then in the implementation of the Text()
widget I set to it a loading text to be shown until the it Future
is done0