Irbisa · Шпаргалка
Сеть и REST
http, Dio, REST API, сериализация JSON, интерцепторы
Какие HTTP-методы и коды ответа нужно знать и как приложение должно реагировать на 401, 403, 404 и 5xx?
Как загрузить данные из API при открытии экрана и показать состояния загрузки, ошибки и данных?
class _UserScreenState extends State<UserScreen> { late Future<User> _user; @override void initState() { super.initState();…Этот
fetchUserработает в демо и падает в продакшене, как только пользователя удаляют: что в нём не так?Future<User> fetchUser(int id) async { final res = await http.get(Uri.parse('$baseUrl/users/$id')); final json = jsonDecode(res.body) as Map<String, dynamic>; return User.fromJson(json);}Поиск начинает возвращать чушь, как только пользователь вводит «C# & Java»: что не так со способом сборки URL этого запроса?
// wrong: '#' starts a fragment, so the server sees only q=Cfinal bad = Uri.parse('https://api.example.com/search?q=$term'); // right: every value is percent-encodedfinal good = Uri.https('api.example.com', '/search', {'q': term, 'page': '1'});…Какой HTTP-клиент и способ сериализации JSON вы выберете для Flutter-приложения и почему?
part 'user.g.dart'; @JsonSerializable()class User { const User({required this.id, required this.name, required this.email});…Найдите баг:
fetchOrdersпадает на каждом вызове, хотя сервер возвращает валидный JSON с нужными полями.class Order { Order({required this.id, required this.total}); final int id; final double total; factory Order.fromJson(Map<String, dynamic> json) => Order(id: json['id'] as int, total: (json['total'] as num).toDouble());} Future<List<Order>> fetchOrders(Dio dio) async { final res = await dio.get<Map<String, dynamic>>('/orders'); final items = res.data!['items'] as List<Map<String, dynamic>>; return items.map(Order.fromJson).toList();}На плохой сети приложение две минуты крутит спиннер, прежде чем хоть что-то упадёт: какие таймауты Dio это лечат и что покрывает каждый?
final dio = Dio(BaseOptions( baseUrl: 'https://api.example.com', connectTimeout: const Duration(seconds: 8), // TCP + TLS handshake sendTimeout: const Duration(seconds: 15), // writing the request body receiveTimeout: const Duration(seconds: 20), // gap between response bytes));…Как реализовать интерцепторы запросов/ответов во Flutter?
class AuthInterceptor extends Interceptor { AuthInterceptor(this._tokens); final TokenStore _tokens; @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) {…Разлогиненные пользователи жалуются, что публичный каталог крутится бесконечно, а у залогиненных всё работает: что не так с этим интерцептором?
dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) async { final token = await storage.read(key: 'access_token'); if (token != null) { options.headers['Authorization'] = 'Bearer $token'; handler.next(options); } },));Как маппить типы DioException в типизированные доменные ошибки?
sealed class AppException implements Exception { const AppException();} class NetworkException extends AppException { const NetworkException(); }class UnauthorizedException extends AppException { const UnauthorizedException(); }…Пользователь уходит с экрана, пока его запрос ещё выполняется. Как отменить запрос?
class OrdersController { OrdersController(this._dio); final Dio _dio; final _cancel = CancelToken(); // one per screen Future<List<Order>?> load() async {…Найдите баг: после быстрого ввода список поиска иногда показывает результаты, которые не совпадают с текстом в поле.
class _SearchState extends State<SearchScreen> { List<Product> _results = []; Future<void> _onQueryChanged(String query) async { final results = await widget.api.search(query); if (!mounted) return; setState(() => _results = results); } @override Widget build(BuildContext context) => Column(children: [ TextField(onChanged: _onQueryChanged), Expanded(child: ProductList(_results)), ]);}Коллега создаёт новый Dio внутри каждого метода репозитория, и задержка выросла на пару сотен миллисекунд на вызов: за что платит приложение?
На нестабильной мобильной сети падает примерно один запрос из двадцати: как добавить ретраи, не перегрузив сервер и не списав с пользователя деньги дважды?
class RetryInterceptor extends Interceptor { RetryInterceptor(this._dio, {this.maxAttempts = 3}); final Dio _dio; final int maxAttempts; @override Future<void> onError(DioException err, ErrorInterceptorHandler handler) async {…Пять экранов одновременно отправляют запросы с протухшим access-токеном, и пользователя выкидывает на экран логина: что не так сделал интерцептор обновления токена?
class RefreshInterceptor extends Interceptor { RefreshInterceptor(this._dio, this._authDio, this._tokens); final Dio _dio, _authDio; // _authDio has no auth interceptor: no re-entry final TokenStore _tokens; Future<String>? _refreshing; @override…Как загружать файлы (multipart/form-data) во Flutter?
Future<void> uploadAvatar( Dio dio, String path, CancelToken cancel, ValueNotifier<double> progress,) async {…Как скачать большой файл с прогресс-баром и возобновить загрузку после обрыва связи?
Future<void> downloadResumable( Dio dio, String url, String path, void Function(double) onProgress) async { final part = File('$path.part'); final have = await part.exists() ? await part.length() : 0; final res = await dio.get<ResponseBody>(…Offset или cursor-пагинация: что просить у бэкенда и когда клиенту запрашивать следующую страницу?
Как кэшировать HTTP-ответы во Flutter-приложении?
Как понять, что устройство ушло в офлайн, и почему одного
connectivity_plusдля этого мало?class Reachability { Reachability(this._dio); final Dio _dio; // connectivity_plus 6+ emits a list: Wi-Fi and VPN can be up together. Stream<bool> get hasInterface => Connectivity()…Когда GraphQL стоит добавлять во Flutter-приложение?
Как подключиться к WebSocket и обрабатывать переподключение?
class LiveFeed { LiveFeed(this.uri); final Uri uri; final events = StreamController<String>.broadcast(); WebSocketChannel? _channel; var _attempt = 0, _closed = false;…Эндпоинт чата стримит токены через server-sent events, вы читаете его через Dio, и поток умирает через десять секунд: что происходит и как его правильно разбирать?
Stream<String> chatEvents(Dio dio, Object body, CancelToken cancel) async* { final res = await dio.post<ResponseBody>( '/chat/stream', data: body, cancelToken: cancel, options: Options(…Экрану нужны детали по 200 элементам:
Future.waitна 200 запросов упирается в рейт-лимит или теряет 199 удачных ответов из-за одной ошибки. Что делать вместо этого?Future<List<Item?>> loadDetails( ItemApi api, List<int> ids, CancelToken cancel) async { final pool = Pool(6); // at most six requests in flight final futures = [ for (final id in ids) pool.withResource(() {…Бэкенд добавил поле, переименовал значение enum и сделал строку nullable. Как ваше приложение это переживёт?
enum OrderStatus { pending, shipped, delivered, unknown } @JsonSerializable()class OrderDto { OrderDto({required this.id, required this.status, this.note});…