Irbisa · Cheatsheet
Networking & REST
http, Dio, REST APIs, JSON serialization, interceptors
Which HTTP methods and status codes should you know, and how should the app react to 401, 403, 404 and 5xx?
How do you load data from an API when a screen opens and show loading, error and data states?
class _UserScreenState extends State<UserScreen> { late Future<User> _user; @override void initState() { super.initState();…This
fetchUserworks in the demo and crashes in production as soon as a user is deleted: what does it get wrong?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);}Search returns nonsense as soon as someone types "C# & Java" into the box: what is wrong with the way that request URL is built?
// 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'});…Which HTTP client and JSON-serialization approach would you pick for a Flutter app, and why?
part 'user.g.dart'; @JsonSerializable()class User { const User({required this.id, required this.name, required this.email});…Find the bug:
fetchOrdersfails on every call, although the server returns valid JSON with the expected fields.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();}The app sits on a spinner for two minutes on a bad connection before anything fails: which Dio timeouts fix that, and what does each one cover?
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));…How do you implement request/response interceptors in Flutter?
class AuthInterceptor extends Interceptor { AuthInterceptor(this._tokens); final TokenStore _tokens; @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) {…Logged-out users say the public catalog spins forever, while signed-in users are fine: what is wrong with this interceptor?
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); } },));How do you map DioException types into typed domain errors?
sealed class AppException implements Exception { const AppException();} class NetworkException extends AppException { const NetworkException(); }class UnauthorizedException extends AppException { const UnauthorizedException(); }…The user leaves a screen while its request is still in flight. How do you cancel it?
class OrdersController { OrdersController(this._dio); final Dio _dio; final _cancel = CancelToken(); // one per screen Future<List<Order>?> load() async {…Find the bug: after fast typing, the search list sometimes shows results that do not match the text in the box.
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)), ]);}A teammate builds a fresh Dio inside every repository method and latency is up a couple of hundred milliseconds per call: what is the app paying for?
One call in twenty fails on a flaky mobile network: how do you add retries without hammering the server or charging a user twice?
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 {…Five screens fire requests at once with an expired access token, and the user lands back on the login screen: what did the refresh interceptor get wrong?
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…How do you upload files (multipart/form-data) in Flutter?
Future<void> uploadAvatar( Dio dio, String path, CancelToken cancel, ValueNotifier<double> progress,) async {…How do you download a large file with a progress bar and resume it after the connection drops?
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 or cursor pagination: which do you ask the backend for, and when should the client request the next page?
How do you cache HTTP responses in a Flutter app?
How do you detect that the device went offline, and why is
connectivity_plusalone not enough?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()…When is GraphQL worth adding to a Flutter app?
How do you connect to a WebSocket and handle reconnection?
class LiveFeed { LiveFeed(this.uri); final Uri uri; final events = StreamController<String>.broadcast(); WebSocketChannel? _channel; var _attempt = 0, _closed = false;…The chat endpoint streams tokens as server-sent events over Dio and the stream dies after ten seconds: what is going on, and how do you parse it?
Stream<String> chatEvents(Dio dio, Object body, CancelToken cancel) async* { final res = await dio.post<ResponseBody>( '/chat/stream', data: body, cancelToken: cancel, options: Options(…A screen needs details for 200 items, and Future.wait over 200 requests either trips the rate limit or loses 199 good results to one failure: what do you do instead?
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(() {…The backend adds a field, renames an enum value and makes a string nullable. How does your app survive that?
enum OrderStatus { pending, shipped, delivered, unknown } @JsonSerializable()class OrderDto { OrderDto({required this.id, required this.status, this.note});…