# HTTP, JSON and FutureBuilder — Flutter

Source: https://www.skillbyai.com/en/flutter/d-http

> Fetching and modelling remote data.

## From JSON to typed models

The **http** package offers simple `get`/`post` calls; **dio** adds interceptors, cancellation and timeouts. Responses arrive as strings: `jsonDecode` from `dart:convert` turns them into `Map<String, dynamic>`, which you convert into **model classes** with `fromJson` factories so the rest of the app is typed. For larger models, **json_serializable** generates that code via `build_runner`. In the UI, **FutureBuilder** shows loading, error and data states from a `Future`; create the Future once (in `initState` or your state layer), not inside `build`, or it will refetch on every rebuild. Always check status codes and handle timeouts and offline errors.

## Network, disk and native code

Apps fetch JSON over HTTP, persist data locally and call native APIs through plugins and platform channels.

![Three ideas: HTTP and JSON, local storage, platform channels and permissions.](assets/figures/flutter/section-7-map.svg) — Figure 7.1 — Network, local storage and native platform access.

## A model, a request and FutureBuilder

http plus a hand-written fromJson.

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

class Todo {
  final int id;
  final String title;
  final bool done;
  const Todo({required this.id, required this.title, required this.done});

  factory Todo.fromJson(Map<String, dynamic> json) => Todo(
        id: json['id'] as int,
        title: json['title'] as String,
        done: json['completed'] as bool,
      );
}

Future<List<Todo>> fetchTodos() async {
  final res = await http
      .get(Uri.parse('https://api.example.com/todos'))
      .timeout(const Duration(seconds: 10));
  if (res.statusCode != 200) {
    throw Exception('Failed: ${res.statusCode}');
  }
  final list = jsonDecode(res.body) as List<dynamic>;
  return list.map((e) => Todo.fromJson(e as Map<String, dynamic>)).toList();
}

class _TodosPageState extends State<TodosPage> {
  late final Future<List<Todo>> _todos = fetchTodos(); // created once

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<List<Todo>>(
      future: _todos,
      builder: (context, snap) {
        if (snap.connectionState != ConnectionState.done) {
          return const Center(child: CircularProgressIndicator());
        }
        if (snap.hasError) return Text('Error: ${snap.error}');
        final todos = snap.data!;
        return ListView.builder(
          itemCount: todos.length,
          itemBuilder: (_, i) => Text(todos[i].title),
        );
      },
    );
  }
}
```

## Generate the boilerplate

With json_serializable, annotate the class with @JsonSerializable(), add part 'todo.g.dart'; and run dart run build_runner build to generate _$TodoFromJson and _$TodoToJson.

**Quiz:** Why should the Future for a FutureBuilder not be created inside build()?

- [ ] It would run on a different isolate
- [ ] FutureBuilder only accepts Streams
- [ ] Futures cannot be created in widgets
- [x] build can run many times, so the request would be repeated on every rebuild

*Answer:* build can run many times, so the request would be repeated on every rebuild. Create it once and pass the same Future on each build.
