Learn Flutter - Networking & Data Handling
Episode 8 of 23

Learn Flutter - Networking & Data Handling

This episode connects your app to the outside world: HTTP requests with the http or dio package, JSON deserialization with dart:convert and json_serializable, async programming with Future and Stream, and production-ready loading states, error handling, and retry.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

A useful app almost always reads from and sends data to a server. Episode 8 opens that door: you'll learn to make HTTP requests, parse JSON into Dart models, understand async programming with Future and Stream, and handle loading, errors, and retries like a production app. Start by installing the package:

Add the http package
flutter pub add http

flutter pub add http adds the http package to pubspec.yaml and immediately runs pub get. For larger needs, the dio package offers interceptors, timeouts, and upload progress — we'll compare the two in a moment.

HTTP Requests with http or dio

A Basic GET Request

With the http package, a simple request is only a few lines:

HTTP GET with the http package
import 'package:http/http.dart' as http;
 
final response = await http.get(
  Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
);
 
if (response.statusCode == 200) {
  print(response.body);
} else {
  throw Exception('Gagal: ${response.statusCode}');
}

await http.get(Uri.parse(...)) returns a Response. Always check statusCode before processing body. Notice that the URL is always wrapped in Uri.parsehttp.get accepts a Uri, not a raw string.

When to Use dio

dio adds features on top of http: interceptors for logging, base options for the base URL, and cleaner timeout handling. For apps with many endpoints and auth needs, dio saves a lot of boilerplate:

Alternative with dio
flutter pub add dio

JSON Deserialization

From JSON String to Model

dart:convert parses raw JSON, then you map it to a Dart model:

Model with manual fromJson
class Post {
  const Post({required this.id, required this.title});
 
  final int id;
  final String title;
 
  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'] as int,
      title: json['title'] as String,
    );
  }
}

Post.fromJson(json) maps JSON keys to model fields. Use jsonDecode(response.body) to turn the string into a Map before calling fromJson.

json_serializable for Larger Scales

Writing fromJson by hand gets tedious when you have many models. The json_serializable package generates it from annotations:

Add JSON codegen
flutter pub add json_annotation
flutter pub add dev:build_runner dev:json_serializable

Run the codegen:

Run build runner
dart run build_runner build

dart run build_runner build generates .g.dart files containing the fromJson and toJson implementations derived from the @JsonSerializable() annotation.

Async Programming with Future and Stream

Future for a Single Result

Future represents an operation that produces one value later. The combination of async and await makes asynchronous code read like synchronous code:

An async function with Future
Future<Post> ambilPost(int id) async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/posts/$id'),
  );
  final json = jsonDecode(response.body) as Map<String, dynamic>;
  return Post.fromJson(json);
}

A function typed Future<Post> tells the caller the result isn't ready yet. await waits for the result without blocking the UI — the key to comfortable asynchronous programming in Flutter.

Stream for Continuous Data

Stream represents a continuous flow of data — for example notifications or progress. In the UI, StreamBuilder re-renders every time the stream sends new data:

Listening to a stream in the UI
StreamBuilder<int>(
  stream: streamKoneksi,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text('Ping: ${snapshot.data} ms');
    }
    return const Text('Menunggu data...');
  },
)

StreamBuilder calls builder again every time streamKoneksi sends a new value. snapshot.hasData and snapshot.data give access to the latest value.

Loading States, Error Handling, and Retry

FutureBuilder with Full Conditions

Use FutureBuilder to show loading, error, and data in a single widget:

FutureBuilder with three conditions
FutureBuilder<Post>(
  future: ambilPost(1),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const CircularProgressIndicator();
    }
    if (snapshot.hasError) {
      return Text('Terjadi kesalahan: ${snapshot.error}');
    }
    return Text(snapshot.data!.title);
  },
)

ConnectionState.waiting indicates the process is still running — show a spinner. snapshot.hasError holds error handling, and snapshot.data is used once the data is ready.

A Safe Retry Pattern

Don't give up on a single network failure. Build a retry helper with a limit:

Retry helper with a limit
Future<T> cobaUlang<T>(Future<T> Function() aksi, {int maks = 3}) async {
  for (var i = 0; i < maks; i++) {
    try {
      return await aksi();
    } catch (e) {
      if (i == maks - 1) rethrow;
      await Future.delayed(Duration(milliseconds: 500 * (i + 1)));
    }
  }
  throw StateError('Tidak terjangkau');
}

cobaUlang calls the action until it succeeds or reaches maks attempts. The delay Duration(milliseconds: 500 * (i + 1)) gives the server time to recover — without hammering it too aggressively.

Conclusion

Key takeaways:

  • http for small projects; dio for interceptor and timeout needs.
  • Always check statusCode and wrap URLs with Uri.parse.
  • Models with manual fromJson or json_serializable codegen.
  • Future for a single result; Stream for continuous data.
  • FutureBuilder handles loading, error, and data in one place.
  • Retry with a limit and exponential backoff.

In the next episode 9 we discuss persistent storage — local storage with shared_preferences and file I/O, local SQLite databases with sqflite and Drift, caching strategies and offline support, and secure storage for tokens and secrets. Your app's data no longer disappears when the app closes.

Learn Flutter - Networking & Data Handling | Learn Flutter