Я пытаюсь лучше понять Futures во Flutter. В этом примере мое приложение выполняет вызов API, чтобы получить некоторую информацию типа Future<String>
. Я хочу отобразить эту информацию в виджете Text()
. Однако, поскольку мой String
заключен в Future
, я не могу поместить эту информацию в свой виджет Text()
, и я не знаю, как с этим справиться, не прибегая к FutureBuilder
для создания небольшого дерева виджетов.
В следующем примере используется FutureBuilder
, и он работает нормально. Обратите внимание, что я закомментировал следующую строку внизу:
Future<String> category = getData();
Можно ли превратить category
в String
и просто поместить это в мой виджет Text()
?
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,
);
}
}
Вот мой Cocktails
класс:
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;
}
}
А вот и мой NetworkHelper
класс:
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';
}
}
}
Лучше всего использовать 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);
}
}
},
),
но если вы не хотите использовать FutureBuilder
, сначала определите строковую переменную, как показано ниже, и измените свой adasd на это:
String category = '';
Future<void> getData() async {
var data = await cocktails.getCocktailByName('margarita');
setState(() {
category = data['drinks'][0]['strCategory'];
});
}
затем вызовите его в initState :
@override
void initState() {
super.initState();
getData();
}
и используйте его так:
@override
Widget build(BuildContext context) {
return Center(
child: Text(category),
);
}
помните, определяйте category
и getData
и cocktails
вне метода сборки, а не внутри него.
Спасибо, но мой FutureBuilder
уже работает. Я искал способ сделать это без использования FutureBuilder
. Я не уверен, что то, о чем я прошу, возможно?
Я обновил свой ответ, зацените, @MySilmaril
Еще раз спасибо. Это именно то, что я искал. Я попробовал это, и это отлично работает, но я думаю, что последую вашему совету и воспользуюсь FutureBuilder
.
@MySilmaril рад помочь, да FutureBuilder
это правильный путь.
Я бы использовал FutureProvider RiverPod перед использованием FutureBuilder в любой день. Гораздо более гибкий и гораздо более чистый синтаксис.
Да, вы можете получить значение Future
и обновить состояние на основе in без использования FutureBuilder
, вызвав Future
в initState()
и используя ключевое слово then
, чтобы обновить состояние, когда Future возвращает снимок.
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() {
super.initState();
getData().then((value) {
setState(() {
text = value;
});
});
}
@override
Widget build(BuildContext context) {
return Text(text ?? 'Loading');
}
}
здесь я сделал переменную text
обнуляемой, затем в реализации виджета Text()
я установил для нее загружаемый текст в качестве значения по умолчанию, которое будет отображаться до тех пор, пока это Future
не будет выполнено0
Спасибо, что поделились, как это сделать, используя ключевое слово then
. До сих пор я использовал async-await, так что это очень полезный пример для меня.
Ничего страшного, удачи в работе
super.initState();
принадлежит первым в initState(), а не последним.
спасибо, я ошибся. Я изменю ответ
getData()
возвращаетFuture<String>
, поэтому я не могу использовать его напрямую вText()
. Другими словами, я ищу способ использоватьcategory
в своем текстовом виджете следующим образом:Text(category)
, не прибегая кFutureBuilder
.