Выстрой профессиональный сетевой вызов с пакетом http: таймаут, коды статуса, типизированное декодирование и исчерпывающие состояния.
Открыть этот урок в KodokonПакет http закрывает самое главное: запросы, заголовки, тело. dio добавляет перехватчики, отмену и повторные попытки, ценой более тяжёлой зависимости - хороший выбор, когда приложение перерастает пару-тройку эндпоинтов. Каким бы ни был клиент, структура серьёзного сетевого вызова не меняется: таймаут, проверка кода статуса HTTP, типизированное декодирование и три состояния экрана (загрузка, ошибка, данные), обработанные явно.
flutter pub add httpimport 'dart:convert';
import 'package:http/http.dart' as http;
class Article {
const Article({required this.title});
factory Article.fromJson(Map<String, dynamic> json) =>
Article(title: json['title'] as String);
final String title;
}
Future<List<Article>> fetchArticles() async {
final uri = Uri.https('api.example.com', '/articles');
final response = await http
.get(uri)
.timeout(const Duration(seconds: 8));
if (response.statusCode != 200) {
throw Exception('HTTP ${response.statusCode}');
}
final body = utf8.decode(response.bodyBytes);
final data = jsonDecode(body) as List<dynamic>;
return [
for (final item in data)
Article.fromJson(item as Map<String, dynamic>),
];
}Два поведения часто удивляют. Первое: http не выбрасывает исключение при статусе 404 или 500 - с точки зрения сети запрос «удался», так что проверять statusCode должен ты. Второе: jsonDecode возвращает dynamic; приводи тип сразу (as List<dynamic>, as Map<String, dynamic>), чтобы вернуть типизацию. Для больших объёмов данных выноси декодирование в изолят через compute, чтобы не подвешивать интерфейс.
Остаётся представить состояние экрана. Булевы флаги вроде isLoading вместе с error != null рано или поздно приводят к несогласованным состояниям (загрузка и ошибка одновременно). Класс sealed делает состояния взаимоисключающими, а switch - исчерпывающим: если завтра ты добавишь состояние, компилятор укажет на каждый экран, который надо обновить.
import 'package:flutter/material.dart';
sealed class ArticlesState {
const ArticlesState();
}
class ArticlesLoading extends ArticlesState {
const ArticlesLoading();
}
class ArticlesError extends ArticlesState {
const ArticlesError(this.message);
final String message;
}
class ArticlesLoaded extends ArticlesState {
const ArticlesLoaded(this.titles);
final List<String> titles;
}
Widget buildArticlesBody(ArticlesState state) {
return switch (state) {
ArticlesLoading() => const Center(
child: CircularProgressIndicator(),
),
ArticlesError(:final message) => Center(
child: Text(message),
),
ArticlesLoaded(:final titles) => ListView.builder(
itemCount: titles.length,
itemBuilder: (context, index) => ListTile(
title: Text(titles[index]),
),
),
};
}http.get получает ответ 500. Что делает пакет http?try/catch.Response: проверять statusCode должен ты.jsonDecode не поддерживает UTF-8.response.body тогда декодируется как latin-1; используй utf8.decode(response.bodyBytes).sealed для моделирования состояния экрана?switch становится исчерпывающим: компилятор укажет на любое необработанное состояние.