Menu
CoddyTech

Champs JSON manquants

Fait partie de la section Données et réseau du Journey Flutter de Coddy. Leçon 19 sur 27.

Les données réelles comportent des lacunes. Dans /books, deux livres ont un "subtitle" et Orgueil et Préjugés n'en a pas. Pour une clé absente, json['subtitle'] renvoie null, et null as String échoue : le chargement de toute la liste échoue avec type 'Null' is not a subtype of type 'String'.

Un type suivi de ? peut aussi contenir null. String?, c'est « une chaîne de caractères, ou rien » :

final String? subtitle;

subtitle: json['subtitle'] as String?,

Dart vous oblige alors à gérer cette lacune partout où vous l'utilisez. ?? fournit une valeur de remplacement si la valeur est null :

Text(book.subtitle ?? 'No subtitle')

Vous pourriez aussi écrire book.subtitle!, ce qui indique à Dart « faites-moi confiance, cette valeur n'est pas null ». Pour des données provenant d'un serveur, c'est une promesse que vous ne pouvez pas tenir : pour le livre sans sous-titre, ! fait planter l'écran. Réservez ! aux endroits que vous avez vérifiés, comme snapshot.data! après hasData.

challenge icon

Défi

Moyen

L’étagère ne se charge pas, car un livre n’a pas de sous-titre. Rends le sous-titre facultatif :

  • le champ est un String?, lis-le avec as String?
  • la liste affiche No subtitle pour un livre qui n’en a pas

Essayez vous-même

import 'dart:convert';

import 'package:flutter/material.dart';

import 'api.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: BooksScreen(),
    );
  }
}

class BooksScreen extends StatefulWidget {
  const BooksScreen({super.key});

  @override
  State<BooksScreen> createState() => _BooksScreenState();
}

class _BooksScreenState extends State<BooksScreen> {
  late Future<List<Book>> _books;

  @override
  void initState() {
    super.initState();
    _books = loadBooks();
  }

  Future<List<Book>> loadBooks() async {
    final body = await fetch('/books');
    final items = jsonDecode(body) as List<dynamic>;
    return items.map((item) => Book.fromJson(item as Map<String, dynamic>)).toList();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Books')),
      body: FutureBuilder<List<Book>>(
        future: _books,
        builder: (context, snapshot) {
          if (snapshot.hasError) {
            return Center(child: Text('Could not load: ${snapshot.error}'));
          }
          if (snapshot.hasData) {
            final books = snapshot.data!;
            return ListView.builder(
              itemCount: books.length,
              itemBuilder: (context, index) {
                final book = books[index];
                return ListTile(
                  title: Text(book.title),
                  subtitle: Text(book.subtitle),
                );
              },
            );
          }
          return const Center(child: CircularProgressIndicator());
        },
      ),
    );
  }
}

class Book {
  Book({required this.title, required this.subtitle});

  final String title;
  final String subtitle;

  factory Book.fromJson(Map<String, dynamic> json) {
    return Book(
      title: json['title'] as String,
      subtitle: json['subtitle'] as String,
    );
  }
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Données et réseau

Entraînez-vous par vous-même : Compilateur Flutter en ligne