Загрузка данных из сети и FutureBuilder

FutureBuilder связывает асинхронные данные с интерфейсом, автоматически показывая загрузку, ошибку или результат.

Суть: вы запрашиваете данные по HTTP, парсите JSON в объекты Dart, а FutureBuilder сам перестраивает интерфейс в зависимости от состояния Future: ожидание, успех или ошибка.

В реальных проектах сетевой слой обычно прячут за классом-репозиторием: виджеты не знают про http и URL, они просто просят у репозитория «дай пользователя», а тот заботится о запросе, парсинге и кешировании. Такое разделение делает код тестируемым и позволяет подменить сеть заглушкой в тестах. Даже если сейчас это кажется избыточным, привыкайте отделять получение данных от их отображения — это окупится в любом растущем приложении.

Настоящее приложение живёт данными из интернета. Цепочка такая: отправить HTTP-запрос пакетом http, получить ответ в формате JSON, распарсить его в модель Dart и показать. Чтобы аккуратно отрисовать все стадии — спиннер во время загрузки, сообщение при ошибке, данные при успехе — Flutter даёт виджет FutureBuilder.

import 'dart:convert';
import 'package:http/http.dart' as http;

class User {
  final String name;
  User(this.name);
  factory User.fromJson(Map<String, dynamic> json) =>
      User(json['name'] as String);
}

Future<User> fetchUser() async {
  final res = await http.get(Uri.parse('https://api.example.com/user'));
  if (res.statusCode == 200) {
    return User.fromJson(jsonDecode(res.body));
  }
  throw Exception('Не удалось загрузить');
}

// в build:
FutureBuilder<User>(
  future: fetchUser(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const CircularProgressIndicator();        // загрузка
    } else if (snapshot.hasError) {
      return Text('Ошибка: ${snapshot.error}');         // ошибка
    } else {
      return Text('Привет, ${snapshot.data!.name}');    // успех
    }
  },
)

Именованный конструктор User.fromJson превращает Map (результат jsonDecode) в типизированный объект. FutureBuilder получает future и через snapshot сообщает текущее состояние.

Как работает FutureBuilder под капотом

FutureBuilder подписывается на переданный Future и перестраивает себя при смене его состояния. snapshot.connectionState сообщает, идёт ли ожидание; hasError — случилась ли ошибка; data — готовый результат. Так вы декларативно описываете все три ветки интерфейса, а синхронизацию берёт на себя Flutter.

  fetchUser() -> Future<User>
        |
        v
   FutureBuilder следит за snapshot:

   waiting    ->  CircularProgressIndicator()   (спиннер)
      |
      v
   hasError   ->  Text('Ошибка: ...')           (сбой сети)
      |
      v
   hasData    ->  Text('Привет, Аня')           (успех)
# Модель парсинга JSON и веток состояния FutureBuilder
import json

raw = '{"name": "Аня", "age": 20}'      # тело ответа сервера

def from_json(raw):
    data = json.loads(raw)               # как jsonDecode в Dart
    return {'name': data['name']}        # как User.fromJson

def render(state, error=None, data=None):
    if state == 'waiting':
        return 'спиннер загрузки'
    if state == 'error':
        return f'Ошибка: {error}'
    return f"Привет, {data['name']}"

print(render('waiting'))
print(render('error', error='нет сети'))
print(render('done', data=from_json(raw)))

Частые ошибки

  • Создавать Future прямо в build у StatefulWidget — он пересоздаётся при каждом перестроении, и запрос летит снова. Храните Future в поле и инициализируйте в initState.
  • Не обрабатывать hasError — при сбое сети пользователь увидит исключение.
  • Обращаться к snapshot.data! до проверки — пока данных нет, это null.

Best practices

  • Всегда показывайте три состояния: загрузка, ошибка, данные — пользователь должен понимать, что происходит.
  • Парсите JSON в типизированные модели через fromJson, а не таскайте сырые Map по приложению.
  • В StatefulWidget кешируйте Future в поле, инициализируя его в initState.

Ручной разбор JSON через fromJson хорош для обучения и небольших моделей, но в крупных проектах его автоматизируют генераторами кода вроде json_serializable или пакетами с неизменяемыми моделями вроде freezed. Они избавляют от рутины и опечаток в разборе десятков полей. Принцип, однако, остаётся тем же, что вы изучили вручную: сырой текст ответа превращается в типизированный объект Dart, с которым дальше работает весь интерфейс.

Итог: FutureBuilder — мост между сетью и интерфейсом: запрос, парсинг JSON в модель и автоматическая отрисовка состояний. Это финальный кусок пазла — теперь вы умеете построить полноценный экран с реальными данными и завершаете курс готовым к собственному приложению.

Проверьте себя
1. Зачем нужен виджет FutureBuilder?
AСоздавать Future
BАвтоматически перестраивать интерфейс под состояние Future: загрузка, ошибка, данные
CПарсить JSON
DХранить навигацию
2. Почему не стоит создавать Future прямо внутри build у StatefulWidget?
AЭто синтаксическая ошибка
Bbuild вызывается часто, и запрос будет повторяться при каждом перестроении
CFuture нельзя хранить
DFutureBuilder это запрещает