Загрузка данных один раз в initState
Часть раздела Данные и сеть путешествия по Flutter на Coddy. Урок 7 из 27.
Flutter снова вызывает build каждый раз, когда экран перерисовывается: после каждого вызова setState, при повороте телефона, при изменении родительского элемента. Это может происходить много раз в секунду. Метод build должен только описывать экран, а не запускать какие-либо действия.
Поэтому вызов fetch, написанный внутри build, выполняется при каждой перерисовке. Каждый вызов создаёт новый запрос к серверу, и экран может переключиться на другой ответ, хотя никто его не запрашивал:
// новый запрос при каждой перерисовке
FutureBuilder<String>(
future: fetch('/tip'),
...
)Создай future один раз при открытии экрана, в initState, и каждый раз передавай FutureBuilder тот же future:
late Future<String> _tip;
@override
void initState() {
super.initState();
_tip = fetch('/tip');
}initState выполняется один раз, перед первым вызовом build. Начни его с super.initState();, как того требует Flutter.
Задание
СреднеНажми на сердечко в верхней панели: подсказка меняется, потому что fetch находится в build. Загрузи подсказку один раз:
- добавь
late Future<String> _tip;в состояние - присвой ей значение в
initStateс помощьюfetch('/tip') - передай
_tipвFutureBuilder
Попробуйте сами
import 'package:flutter/material.dart';
import 'api.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: TipScreen(),
);
}
}
class TipScreen extends StatefulWidget {
const TipScreen({super.key});
@override
State<TipScreen> createState() => _TipScreenState();
}
class _TipScreenState extends State<TipScreen> {
bool liked = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Tip of the Day'),
actions: [
IconButton(
icon: Icon(liked ? Icons.favorite : Icons.favorite_border),
onPressed: () {
setState(() {
liked = !liked;
});
},
),
],
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: FutureBuilder<String>(
future: fetch('/tip'),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(
snapshot.data!,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 24),
);
}
return const CircularProgressIndicator();
},
),
),
),
);
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Данные и сеть
1Future (асинхронные результаты)
Значение, которое появится позжеОжидание с async и awaitФункции, возвращающие FutureПрактический API Coddy2Экраны загрузки
Индикатор во время загрузкиЗнакомство с FutureBuilderЗагрузка данных один раз в initStateПерезагрузка данных кнопкойОбновление свайпом вниз3Когда вызовы завершаются ошибкой
Перехват ошибок с помощью tryВыбрасывание ошибкиОчистка с помощью finallyСостояние ошибки в FutureBuilderКнопка «Попробовать снова»Потренируйтесь самостоятельно: Онлайн-компилятор Flutter