Menu
CoddyTech

Campos JSON faltantes

Parte de la sección Datos y red del Journey de Flutter de Coddy. Lección 19 de 27.

Los datos reales tienen lagunas. En /books, dos libros tienen un "subtitle" y Orgullo y prejuicio no tiene ninguno. Para una clave que no está presente, json['subtitle'] da null, y null as String falla: no se carga la lista completa con type 'Null' is not a subtype of type 'String'.

Un tipo con un ? después también puede contener null. String? significa «una cadena o nada»:

final String? subtitle;

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

Dart entonces te obliga a gestionar esa laguna dondequiera que uses el valor. ?? proporciona un valor alternativo para null:

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

También podrías escribir book.subtitle!, que le dice a Dart «confía en mí, no es null». Para datos de un servidor, esa es una promesa que no puedes cumplir: en el libro que no tiene subtítulo, ! bloquea la pantalla. Reserva ! para los lugares que hayas comprobado, como snapshot.data! después de hasData.

challenge icon

Desafío

Intermedio

La estantería no se carga porque un libro no tiene subtítulo. Haz que el subtítulo sea opcional:

  • el campo es un String?; léelo como as String?
  • la lista muestra No subtitle para un libro que no tiene subtítulo

Pruébalo tú mismo

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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Datos y red

Practica por tu cuenta: Compilador de Flutter online