Menu
CoddyTech

Các trường JSON bị thiếu

Thuộc phần Dữ liệu và mạng trong hành trình Flutter của Coddy. Bài học 19 trên 27.

Dữ liệu thực tế thường có chỗ thiếu. Trong /books, hai cuốn sách có "subtitle", còn Pride and Prejudice thì không. Với khóa không có trong dữ liệu, json['subtitle'] trả về null, còn null as String gây lỗi: toàn bộ danh sách không tải được với lỗi type 'Null' is not a subtype of type 'String'.

Kiểu dữ liệu có thêm ? ở phía sau cũng có thể chứa null. String? có nghĩa là «một chuỗi hoặc không có gì»:

final String? subtitle;

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

Dart buộc bạn xử lý chỗ thiếu dữ liệu ở bất cứ nơi nào bạn dùng giá trị đó. ?? cung cấp giá trị dự phòng khi gặp null:

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

Bạn cũng có thể viết book.subtitle!, để báo với Dart rằng «cứ tin mình, giá trị này không phải null». Với dữ liệu từ máy chủ, bạn không thể đảm bảo điều đó: khi gặp cuốn sách không có phụ đề, ! sẽ làm màn hình gặp sự cố. Chỉ dùng ! ở những chỗ bạn đã kiểm tra, chẳng hạn snapshot.data! sau khi kiểm tra hasData.

challenge icon

Thử thách

Trung bình

Kệ sách không tải được vì một quyển sách không có phụ đề. Hãy đặt phụ đề thành tùy chọn:

  • trường này có kiểu String?, đọc bằng as String?
  • danh sách hiển thị No subtitle cho quyển sách không có phụ đề

Tự làm thử

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 iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Dữ liệu và mạng

Tự luyện tập: Trình biên dịch Flutter online