Mempelajari lebih dalam pola dan kumpulan data Dart

1. Pengantar

Dart 3 memperkenalkan pola ke bahasa pemrograman, sebuah kategori besar tata bahasa yang baru. Selain adanya cara baru untuk menulis kode Dart ini, ada juga beberapa peningkatan bahasa lainnya, termasuk kumpulan data untuk menggabungkan berbagai jenis data, pengubah class untuk mengontrol akses, serta ekspresi switch dan pernyataan if-case baru.

Fitur-fitur ini memberi Anda lebih banyak pilihan saat menulis kode Dart. Dalam codelab ini, Anda akan mempelajari cara menggunakannya untuk membuat kode Anda lebih ringkas, sederhana, dan fleksibel.

Codelab ini berasumsi bahwa Anda cukup familier dengan Flutter dan Dart, meskipun tidak wajib. Sebelum memulai, sebaiknya ingat kembali dasar-dasarnya dengan membuka referensi berikut:

Yang akan Anda bangun

Codelab ini berisi cara membuat aplikasi yang menampilkan dokumen JSON di Flutter. Aplikasi ini membuat simulasi JSON yang berasal dari sumber eksternal. JSON berisi data dokumen seperti tanggal diubah, judul, header, dan paragraf. Anda menulis kode untuk menggabungkan data dengan rapi menjadi kumpulan data, sehingga data tersebut dapat ditransfer ke dan diekstrak di mana pun widget Flutter memerlukannya.

Kemudian, gunakan pola untuk membangun widget yang sesuai ketika nilainya cocok dengan pola tersebut. Anda juga akan melihat cara menggunakan pola untuk mengurai data menjadi variabel lokal.

Aplikasi final yang Anda bangun dalam codelab ini, dokumen dengan judul, tanggal terakhir diubah, header, dan paragraf.

Yang akan Anda pelajari

  • Cara membuat data yang menyimpan beberapa nilai dengan jenis yang berbeda.
  • Cara menampilkan beberapa nilai dari sebuah fungsi menggunakan data.
  • Cara menggunakan pola untuk mencocokkan, memvalidasi, dan mengurai data dari kumpulan data dan objek lainnya.
  • Cara mengikat nilai yang polanya cocok ke variabel baru atau yang sudah ada.
  • Cara menggunakan kemampuan pernyataan switch, ekspresi switch, dan pernyataan if-case baru.
  • Cara memanfaatkan pemeriksaan seluruh kemungkinan untuk memastikan bahwa setiap kasus tertangani di pernyataan switch atau ekspresi switch.

2. Menyiapkan lingkungan Anda

  1. Instal Flutter SDK.
  2. Siapkan editor seperti Visual Studio Code (VS Code).
  3. Lihat langkah-langkah Penyiapan platform untuk setidaknya satu platform target (iOS, Android, Desktop, atau browser web).

3. Membuat project

Sebelum mulai mempelajari pola, kumpulan data, dan fitur baru lainnya, luangkan waktu untuk menyiapkan lingkungan Anda dan project Flutter sederhana yang kodenya akan Anda buat.

Menggunakan Dart

  • Untuk memastikan Anda sudah menggunakan Dart 3, jalankan perintah berikut:
flutter channel stable
flutter upgrade
dart --version # This should print "Dart SDK version: 3.0.0" or higher

Membuat project Flutter

  1. Gunakan perintah flutter create untuk membuat project baru bernama patterns_codelab. Tanda --empty mencegah pembuatan aplikasi penghitung standar di file lib/main.dart, yang pada akhirnya perlu Anda hapus.
flutter create --empty patterns_codelab
  1. Kemudian, buka direktori patterns_codelab menggunakan VS Code.
code patterns_codelab

Screenshot VS Code yang menampilkan project yang dibuat dengan perintah 'flutter create'.

Menentukan versi SDK minimum

  • Tentukan batas versi SDK untuk project Anda agar menggunakan Dart 3 atau yang lebih baru.

pubspec.yaml

environment:
  sdk: ^3.0.0

4. Menyiapkan project

Di langkah ini, Anda akan membuat dua file Dart:

  • File main.dart yang berisi widget untuk aplikasi.
  • File data.dart yang menyediakan data aplikasi.

Menentukan data untuk aplikasi

  • Buat file baru, lib/data.dart, lalu tambahkan kode berikut ke sana:

lib/data.dart

import 'dart:convert';

class Document {
  final Map<String, Object?> _json;
  Document() : _json = jsonDecode(documentJson);
}

const documentJson = '''
{
  "metadata": {
    "title": "My Document",
    "modified": "2023-05-10"
  },
  "blocks": [
    {
      "type": "h1",
      "text": "Chapter 1"
    },
    {
      "type": "p",
      "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
    },
    {
      "type": "checkbox",
      "checked": false,
      "text": "Learn Dart 3"
    }
  ]
}
''';

Bayangkan suatu program yang menerima data dari sumber eksternal, seperti streaming I/O atau permintaan HTTP. Dalam codelab ini, Anda akan menyederhanakan kasus penggunaan yang lebih realistis tersebut dengan meniru data JSON yang masuk dengan string multibaris dalam variabel documentJson.

Data JSON ditentukan di class Document, dan dalam codelab ini nanti, Anda akan menambahkan fungsi yang menampilkan data dari JSON yang diurai. Class ini menentukan dan melakukan inisialisasi pada kolom _json di konstruktornya.

Menjalankan aplikasi

Perintah flutter create membuat file lib/main.dart sebagai bagian dari struktur file Flutter default.

  1. Untuk membuat titik awal aplikasi, ganti konten main.dart dengan kode berikut:

lib/main.dart

import 'package:flutter/material.dart';

import 'data.dart';

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

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

 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     theme: ThemeData(useMaterial3: true),
     home: DocumentScreen(
       document: Document(),
     ),
   );
 }
}

class DocumentScreen extends StatelessWidget {
 final Document document;

 const DocumentScreen({
   required this.document,
   Key? key,
 }) : super(key: key);

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text('Title goes here'),
     ),
     body: Column(
       children: [
         Center(
           child: Text('Body goes here'),
         ),
       ],
     ),
   );
 }
}

Tambahkan dua widget berikut ke aplikasi:

  • DocumentApp menyiapkan versi terbaru Desain Material untuk penerapan tema UI.
  • DocumentScreen menyediakan tata letak visual halaman menggunakan widget Scaffold.
  1. Untuk memastikan semuanya berfungsi lancar, jalankan aplikasi di mesin host dengan mengklik Run and Debug:

Gambar tombol 'Run and debug', tersedia di bagian 'Run and debug' di kolom aktivitas sebelah kiri.

  1. Secara default, Flutter akan memilih platform target mana pun yang tersedia. Untuk mengubah platform target, pilih platform saat ini di Status Bar:

Screenshot pemilih platform target di VS Code.

Anda akan melihat frame kosong dengan elemen title dan body yang ditentukan di widget DocumentScreen:

Screenshot aplikasi yang dibangun di langkah ini.

5. Membuat dan menampilkan kumpulan data

Di langkah ini, Anda akan menggunakan kumpulan data untuk menampilkan beberapa nilai dari panggilan fungsi. Lalu, Anda akan memanggil fungsi tersebut di widget DocumentScreen agar dapat mengakses nilai dan mencerminkannya di UI.

Membuat dan menampilkan data

  • Di data.dart, tambahkan fungsi baru ke class Dokumen bernama getMetadata yang menampilkan data:

lib/data.dart

(String, {DateTime modified}) getMetadata() {
  var title = "My Document";
  var now = DateTime.now();

  return (title, modified: now);
}

Jenis nilai yang ditampilkan untuk fungsi ini adalah data yang berisi dua kolom, satu dengan jenis String, dan satunya lagi dengan jenis DateTime.

Pernyataan return membentuk data baru dengan mengapit dua nilai tersebut dengan tanda kurung, (title, modified: now).

Kolom pertama adalah posisi dan tanpa nama, lalu kolom kedua bernama modified.

Mengakses kolom kumpulan data

  1. Di widget DocumentScreen, panggil getMetadata() di metode build untuk mendapatkan kumpulan data Anda dan mengakses nilainya:

lib/main.dart

  @override
  Widget build(BuildContext context) {
    var metadataRecord = document.getMetadata();

    return Scaffold(
      appBar: AppBar(
        title: Text(metadataRecord.$1),
      ),
      body: Column(
        children: [
          Center(
            child: Text(
              'Last modified ${metadataRecord.modified}',
            ),
          ),
        ],
      ),
    );
  }

Fungsi getMetadata() menampilkan data, yang ditetapkan ke variabel lokal metadataRecord. Kumpulan data adalah cara singkat dan mudah untuk menampilkan beberapa nilai dari satu panggilan fungsi lalu menetapkannya ke sebuah variabel.

Untuk mengakses satuan kolom yang ada dalam kumpulan data, Anda dapat menggunakan sintaksis pengambil bawaan dari kumpulan data tersebut.

  • Untuk mendapatkan kolom posisi (kolom tanpa nama, seperti title), gunakan pengambil $<num> di kumpulan data tersebut. Tindakan ini hanya akan menampilkan kolom tanpa nama.
  • Kolom bernama seperti modified tidak memiliki pengambil posisi, jadi Anda dapat langsung menggunakan namanya, seperti metadataRecord.modified.

Guna menentukan nama pengambil untuk kolom posisi, mulailah dengan $1 lalu lewati kolom bernama. Contoh:

var record = (named: ‘v', ‘y', named2: ‘x', ‘z');
print(record.$1); // prints y
print(record.$2) // prints z
  1. Lakukan hot reload untuk melihat nilai JSON yang ditampilkan di aplikasi. Plugin Dart VS Code akan melakukan hot reload setiap kali Anda menyimpan file.

Screenshot aplikasi, yang menampilkan judul dan tanggal diubah.

Anda dapat melihat bahwa setiap kolom memang mempertahankan jenisnya.

  • Metode Text() mengambil String sebagai argumen pertamanya.
  • Kolom modified adalah DateTime, dan dikonversi menjadi String menggunakan interpolasi string.

Cara lain dengan jenis yang aman untuk menampilkan berbagai jenis data adalah dengan menentukan class, yang bentuknya lebih panjang.

6. Mencocokkan dan mengurai dengan pola

Kumpulan data dapat secara efisien mengumpulkan berbagai jenis data dan dengan mudah menyalurkannya. Sekarang, tingkatkan kode Anda menggunakan pola.

Pola mewakili struktur yang dapat diambil oleh satu atau beberapa nilai, seperti cetak biru. Pola akan membandingkan dengan nilai sebenarnya untuk menentukan apakah ada kecocokan.

Beberapa pola, jika menemukan kecocokan, akan mengurai nilai yang cocok dengan mengambil data dari sana. Penguraian memungkinkan Anda mengekstrak nilai dari sebuah objek untuk ditetapkan ke variabel lokal, atau menjalankan pencocokan lebih lanjut.

Mengurai data ke variabel lokal

  1. Faktorkan ulang metode build dari DocumentScreen untuk memanggil getMetadata() dan menggunakannya untuk melakukan inisialisasi pada deklarasi variabel pola:

lib/main.dart

  @override
  Widget build(BuildContext context) {
    var (title, :modified) = document.getMetadata(); // New

    return Scaffold(
      appBar: AppBar(
        title: Text(title), // New
      ),
      body: Column(
        children: [
          Center(
            child: Text(
              'Last modified $modified', // New
            ),
          ),
        ],
      ),
    );
  }

Pola kumpulan data (title, :modified) berisi dua pola variabel yang cocok dengan kolom kumpulan data yang ditampilkan oleh getMetadata().

  • Ekspresinya cocok dengan subpola tersebut karena hasilnya adalah data dengan dua kolom, salah satunya bernama modified.
  • Karena ada kecocokan, pola deklarasi variabel mengurai ekspresi tersebut, guna mengakses nilainya dan mengikatnya ke variabel lokal baru dari jenis dan nama yang sama, String title dan DateTime modified.

Sintaksis pola variabel :modified merupakan bentuk singkat dari modified: modified. Jika ingin variabel lokal baru dengan nama yang berbeda, Anda dapat menulis modified: localModified.

  1. Lakukan hot reload untuk melihat hasil yang sama seperti di langkah sebelumnya. Perilakunya sama persis; Anda hanya mempersingkat kode tersebut.

7. Menggunakan pola untuk mengekstrak data

Dalam konteks tertentu, pola tidak hanya mencocokkan dan mengurai tetapi juga dapat membuat keputusan tentang apa yang dilakukan kode, berdasarkan apakah pola tersebut cocok atau tidak. Pola ini disebut pola refutable.

Pola deklarasi variabel yang Anda gunakan di langkah sebelumnya merupakan pola irrefutable: nilainya harus cocok dengan pola, jika tidak, akan timbul error dan penguraian tidak akan dilakukan. Coba pikirkan tugas atau deklarasi variabel apa pun; Anda tidak dapat menetapkan nilai ke variabel jika jenisnya tidak sama.

Di sisi lain, pola refutable digunakan dalam konteks alur kontrol:

  • Pola tersebut memperkirakan bahwa beberapa nilai yang dibandingkan tidak akan cocok.
  • Pola tersebut dimaksudkan untuk memengaruhi alur kontrol, berdasarkan apakah nilainya cocok atau tidak.
  • Pola tersebut tidak mengganggu eksekusi dengan menampilkan error jika tidak cocok, tetapi hanya akan meneruskan ke pernyataan berikutnya.
  • Pola tersebut dapat mengurai dan mengikat variabel yang hanya dapat digunakan jika ada kecocokan

Membaca nilai JSON tanpa pola

Di bagian ini, Anda akan membaca data tanpa pencocokan pola guna melihat bagaimana pola dapat membantu Anda bekerja dengan data JSON.

  • Ganti getMetadata() versi sebelumnya dengan versi yang membaca nilai dari peta _json. Salin dan tempel versi getMetadata() ini ke class Document:

lib/data.dart

(String, {DateTime modified}) getMetadata() {
  if (_json.containsKey('metadata')) {
    var metadataJson = _json['metadata'];
    if (metadataJson is Map) {
      var title = metadataJson['title'] as String;
      var localModified = DateTime.parse(metadataJson['modified'] as String);
      return (title, modified: localModified);
    }
  }
  throw const FormatException('Unexpected JSON');
}

Kode ini memvalidasi bahwa data terstruktur dengan benar tanpa menggunakan pola. Di langkah berikutnya, Anda akan menggunakan pencocokan pola untuk melakukan validasi yang sama menggunakan lebih sedikit kode. Perlu dilakukan tiga pemeriksaan sebelum melakukan hal lain:

  • JSON berisi struktur data yang Anda harapkan: if (_json.containsKey('metadata'))
  • Data tersebut merupakan jenis yang Anda harapkan: if (metadataJson is Map)
  • Data tersebut bukan null, yang secara implisit telah dipastikan di pemeriksaan sebelumnya.

Membaca nilai JSON menggunakan pola peta

Dengan pola refutable, Anda dapat memverifikasi bahwa JSON memiliki struktur yang diharapkan menggunakan pola peta.

  • Ganti getMetadata() versi sebelumnya dengan kode ini:

lib/data.dart

  (String, {DateTime modified}) getMetadata() {
    if (_json
        case {
          'metadata': {
            'title': String title,
            'modified': String localModified,
          }
        }) {
      return (title, modified: DateTime.parse(localModified));
    } else {
      throw const FormatException('Unexpected JSON');
    }
  }

Di sini, Anda melihat pernyataan if jenis baru (diperkenalkan di Dart 3), if-case. Konten kasus hanya dijalankan jika polanya cocok dengan data di _json. Kecocokan ini menjalankan pemeriksaan yang sama seperti yang Anda tulis di versi pertama getMetadata() guna memvalidasi JSON yang masuk. Kode ini memvalidasi hal berikut:

  • _json merupakan jenis Peta.
  • _json berisi kunci metadata.
  • _json bukan null.
  • _json['metadata'] juga merupakan jenis Peta.
  • _json['metadata'] berisi kunci title dan modified.
  • title dan localModified merupakan string, bukan null.

Jika nilainya tidak cocok, pola akan menolak (menolak untuk melanjutkan eksekusi) dan beralih ke klausa else. Jika pencocokan berhasil, pola tersebut akan mengurai nilai title dan modified dari peta, lalu mengikatnya ke variabel lokal baru.

Untuk melihat daftar pola lengkap, buka tabel di bagian Pola di spesifikasi fitur.

8. Menyiapkan aplikasi untuk lebih banyak pola

Sejauh ini, Anda menangani data JSON bagian metadata. Di langkah ini, Anda akan sedikit mempertajam logika bisnis Anda guna menangani data dalam daftar blocks lalu merendernya ke dalam aplikasi.

{
  "metadata": {
    // ...
  },
  "blocks": [
    {
      "type": "h1",
      "text": "Chapter 1"
    },
    // ...
  ]
}

Membuat class yang menyimpan data

  • Tambahkan class baru, Block, ke data.dart, yang digunakan untuk membaca dan menyimpan data untuk salah satu blok di data JSON.

lib/data.dart

class Block {
  final String type;
  final String text;
  Block(this.type, this.text);

  factory Block.fromJson(Map<String, dynamic> json) {
    if (json case {'type': var type, 'text': var text}) {
      return Block(type, text);
    } else {
      throw const FormatException('Unexpected JSON format');
    }
  }
}

Konstruktor factory fromJson() menggunakan if-case yang sama dengan pola peta yang pernah Anda lihat sebelumnya.

Perhatikan bahwa json cocok dengan pola peta, meskipun salah satu kuncinya, checked, tidak diperhitungkan di pola tersebut. Pola peta mengabaikan semua entri dalam objek peta yang secara eksplisit tidak diperhitungkan di pola tersebut.

Menampilkan daftar objek Blok

  • Selanjutnya, tambahkan fungsi baru, getBlocks(), ke class Document. getBlocks() akan mengurai JSON menjadi beberapa instance class Block dan menampilkan daftar blok untuk dirender di UI Anda:

lib/data.dart

  List<Block> getBlocks() {
    if (_json case {'blocks': List blocksJson}) {
      return <Block>[
        for (var blockJson in blocksJson) Block.fromJson(blockJson)
      ];
    } else {
      throw const FormatException('Unexpected JSON format');
    }
  }

Fungsi getBlocks() menampilkan daftar objek Block, yang nantinya Anda gunakan untuk membangun UI. Pernyataan if-case yang familier memvalidasi dan mentransmisikan nilai metadata blocks ke List baru bernama blocksJson (tanpa adanya pola, Anda memerlukan metode toList() untuk melakukan transmisi).

Literal daftar berisi koleksi agar dapat mengisi daftar baru dengan objek Block.

Bagian ini tidak memperkenalkan fitur terkait pola apa pun yang belum pernah Anda coba dalam codelab ini. Di langkah berikutnya, Anda akan bersiap merender item daftar di UI.

9. Menggunakan pola untuk menampilkan dokumen

Anda kini berhasil mengurai dan merekomposisi data JSON, menggunakan pernyataan if-case dan pola refutable. Namun, if-case hanyalah salah satu peningkatan untuk mengontrol struktur alur yang masuk bersama pola. Sekarang Anda akan menerapkan pengetahuan tentang pola refutable ke pernyataan switch.

Mengontrol apa yang dirender menggunakan pola dengan pernyataan switch

  • Di main.dart, buat widget baru, BlockWidget, yang akan menentukan gaya setiap blok berdasarkan kolom type-nya.

lib/main.dart

class BlockWidget extends StatelessWidget {
  final Block block;

  const BlockWidget({
    required this.block,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    TextStyle? textStyle;
    switch (block.type) {
      case 'h1':
        textStyle = Theme.of(context).textTheme.displayMedium;
      case 'p' || 'checkbox':
        textStyle = Theme.of(context).textTheme.bodyMedium;
      case _:
        textStyle = Theme.of(context).textTheme.bodySmall;
    }

    return Container(
      margin: const EdgeInsets.all(8),
      child: Text(
        block.text,
        style: textStyle,
      ),
    );
  }
}

Pernyataan switch di metode build mengeksekusi switch di kolom type dari objek block.

  1. Pernyataan kasus pertama menggunakan pola string konstan. Polanya cocok jika block.type setara dengan nilai konstan h1.
  2. Pernyataan kasus kedua menggunakan pola logical-or dengan dua pola string konstan sebagai subpolanya. Polanya cocok jika block.type sesuai dengan subpola p atau checkbox.
  1. Kasus terakhir adalah pola karakter pengganti, _. Karakter pengganti di kasus switch cocok dengan yang lain. Karakter ini berperilaku sama seperti klausa default, yang masih diizinkan dalam pernyataan switch (hanya saja bentuknya sedikit lebih panjang).

Karakter pengganti dapat digunakan di mana pun pola diizinkan—misalnya, di pola deklarasi variabel: var (title, _) = document.getMetadata();

Dalam konteks ini, karakter pengganti tidak mengikat variabel apa pun. Pola ini menghapus kolom kedua.

Di bagian berikutnya, Anda akan mempelajari fitur switch lainnya setelah menampilkan objek Block.

Menampilkan konten dokumen

Buat variabel lokal yang berisi daftar objek Block dengan memanggil getBlocks() di metode build widget DocumentScreen.

  1. Ganti metode build yang sudah ada di DocumentationScreen dengan versi ini:

lib/main.dart

  @override
  Widget build(BuildContext context) {
    var (title, :modified) = document.getMetadata();
    var blocks = document.getBlocks(); // New

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: [
          // New
          Text('Last modified: $modified'),
          Expanded(
            child: ListView.builder(
              itemCount: blocks.length,
              itemBuilder: (context, index) {
                return BlockWidget(block: blocks[index]);
              },
            ),
          ),
        ],
      ),
    );
  }

Baris BlockWidget(block: blocks[index]) membentuk widget BlockWidget untuk setiap item dalam daftar blok yang ditampilkan dari metode getBlocks().

  1. Jalankan aplikasi, lalu Anda akan melihat blok tersebut muncul di layar:

Screenshot aplikasi yang menampilkan konten dari bagian 'blok' data JSON.

10. Menggunakan ekspresi switch

Pola menambahkan banyak kemampuan ke switch dan case. Untuk membuatnya dapat digunakan di lebih banyak tempat, Dart memiliki ekspresi switch. Serangkaian kasus dapat langsung memberikan nilai ke pernyataan return atau tugas variabel.

Mengonversi pernyataan switch menjadi ekspresi switch

Penganalisis Dart memberikan bantuan untuk memudahkan Anda membuat perubahan pada kode.

  1. Arahkan kursor ke pernyataan switch dari bagian sebelumnya.
  2. Klik ikon bola lampu untuk melihat bantuan yang tersedia.
  3. Pilih bantuan Convert to switch expression.

Screenshot bantuan 'convert to switch expression' yang tersedia di VS Code.

Versi baru kode ini terlihat seperti ini:

TextStyle? textStyle;
textStyle = switch (block.type) {
  'h1' => Theme.of(context).textTheme.displayMedium,
  'p' || 'checkbox' => Theme.of(context).textTheme.bodyMedium,
  _ => Theme.of(context).textTheme.bodySmall
};

Ekspresi switch terlihat mirip dengan pernyataan switch, tetapi ekspresi ini menghapus kata kunci case dan menggunakan => untuk memisahkan pola dari konten kasus. Tidak seperti pernyataan switch, ekspresi switch menampilkan nilai dan dapat digunakan di mana pun ekspresi dapat digunakan.

11. Menggunakan pola objek

Dart adalah bahasa pemrograman berorientasi objek, jadi pola ini berlaku untuk semua objek. Di langkah ini, Anda akan mengeksekusi switch di pola objek dan mengurai properti objek untuk meningkatkan logika rendering tanggal di UI Anda.

Mengekstrak properti dari pola objek

Di bagian ini, Anda akan meningkatkan tampilan tanggal terakhir diubah menggunakan pola.

  • Tambahkan metode formatDate ke main.dart:

lib/main.dart

String formatDate(DateTime dateTime) {
  var today = DateTime.now();
  var difference = dateTime.difference(today);

  return switch (difference) {
    Duration(inDays: 0) => 'today',
    Duration(inDays: 1) => 'tomorrow',
    Duration(inDays: -1) => 'yesterday',
    Duration(inDays: var days, isNegative: true) => '${days.abs()} days ago',
    Duration(inDays: var days) => '$days days from now',
  };
}

Metode ini menampilkan ekspresi switch yang dieksekusi di nilai difference, objek Duration. Metode ini mewakili jangka waktu antara nilai today dan modified dari data JSON.

Setiap kasus ekspresi switch menggunakan pola objek yang cocok dengan memanggil pengambil di properti objek inDays dan isNegative. Sintaksisnya mungkin terlihat seperti sedang membentuk objek Durasi, tetapi sebenarnya sedang mengakses kolom pada objek difference.

Tiga kasus pertama menggunakan subpola konstan 0, 1, dan -1 agar cocok dengan properti objek inDays dan menampilkan string yang sesuai.

Dua kasus terakhir menangani durasi selain hari ini, kemarin, dan besok:

  • Jika properti isNegative cocok dengan pola konstan boolean true, artinya tanggal diubahnya ada pada masa lampau, days ago akan ditampilkan.
  • Jika kasus tersebut tidak menemukan perbedaannya, berarti durasinya harus berupa jumlah hari positif (tanpa perlu memverifikasi secara eksplisit dengan isNegative: false), jadi tanggal diubahnya ada pada masa mendatang dan menampilkan days from now.

Menambahkan logika pemformatan untuk minggu

  • Tambahkan dua kasus baru ke fungsi pemformatan Anda untuk mengidentifikasi durasi yang lebih lama dari 7 hari, sehingga UI dapat menampilkannya sebagai weeks:

lib/main.dart

String formatDate(DateTime dateTime) {
  var today = DateTime.now();
  var difference = dateTime.difference(today);

  return switch (difference) {
    Duration(inDays: 0) => 'today',
    Duration(inDays: 1) => 'tomorrow',
    Duration(inDays: -1) => 'yesterday',
    Duration(inDays: var days) when days > 7 => '${days ~/ 7} weeks from now', // New
    Duration(inDays: var days) when days < -7 => '${days.abs() ~/ 7} weeks ago', // New
      Duration(inDays: var days, isNegative: true) => '${days.abs()} days ago',
      Duration(inDays: var days) => '$days days from now',
  };
}

Kode ini memperkenalkan klausa guard:

  • Klausa guard menggunakan kata kunci when setelah sebuah pola kasus.
  • Klausa ini dapat digunakan dalam if-case, pernyataan switch, dan ekspresi switch.
  • Klausa ini hanya menambahkan kondisi ke sebuah pola setelah dicocokkan.
  • Jika hasil evaluasi klausa guard salah, seluruh pola akan ditolak, dan eksekusi akan dilanjutkan ke kasus berikutnya.

Menambahkan tanggal dengan pemformatan baru ke UI

  1. Terakhir, update metode build di DocumentScreen untuk menggunakan fungsi formatDate:

lib/main.dart

  @override
  Widget build(BuildContext context) {
    var (title, :modified) = document.getMetadata();
    var formattedModifiedDate = formatDate(modified); // New
    var blocks = document.getBlocks();

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: [
          Text('Last modified: $formattedModifiedDate'), // New
          Expanded(
            child: ListView.builder(
              itemCount: blocks.length,
              itemBuilder: (context, index) =>
                BlockWidget(block: blocks[index]),
            ),
          ),
        ],
      ),
    );
  }
  1. Lakukan hot reload untuk melihat perubahan di aplikasi Anda:

Screenshot aplikasi yang menampilkan string 'Last modified: 2 weeks ago' menggunakan fungsi formatDate().

12. Mengunci class untuk eksekusi switch menyeluruh

Perhatikan bahwa Anda tidak menggunakan kasus default atau karakter pengganti pada switch terakhir. Meskipun selalu menyertakan kasus untuk nilai yang mungkin gagal adalah praktik yang bagus, hal ini tidak wajib dilakukan untuk contoh sederhana seperti ini karena Anda tahu bahwa kasus yang ditetapkan akan mempertimbangkan semua kemungkinan nilai yang dapat diambil inDays.

Jika setiap kasus di pernyataan switch telah ditangani, ini disebut switch menyeluruh. Misalnya, eksekusi switch di jenis bool dianggap menyeluruh saat proses tersebut memiliki kasus untuk true dan false. Eksekusi switch di jenis enum juga bersifat menyeluruh ketika ada kasus untuk setiap nilai enum, karena enum mewakili jumlah tetap dari nilai konstan.

Dart 3 memperluas pemeriksaan seluruh kemungkinan ke objek dan hierarki class dengan pengubah class sealed baru. Faktorkan ulang class Block Anda sebagai superclass yang terkunci.

Membuat subclass

  • Di data.dart, buat tiga class baru—HeaderBlock, ParagraphBlock, dan CheckboxBlock—yang memperluas Block:

lib/data.dart

class HeaderBlock extends Block {
  final String text;
  HeaderBlock(this.text);
}

class ParagraphBlock extends Block {
  final String text;
  ParagraphBlock(this.text);
}

class CheckboxBlock extends Block {
  final String text;
  final bool isChecked;
  CheckboxBlock(this.text, this.isChecked);
}

Setiap class ini sesuai dengan nilai type yang berbeda dari JSON asli: 'h1', 'p', dan 'checkbox'.

Mengunci superclass

  • Tandai class Block sebagai sealed. Kemudian, faktorkan ulang if-case sebagai ekspresi switch yang menampilkan subclass yang sesuai dengan type yang ditetapkan di JSON:

lib/data.dart

sealed class Block {
  Block();

  factory Block.fromJson(Map<String, Object?> json) {
    return switch (json) {
      {'type': 'h1', 'text': String text} => HeaderBlock(text),
      {'type': 'p', 'text': String text} => ParagraphBlock(text),
      {'type': 'checkbox', 'text': String text, 'checked': bool checked} =>
        CheckboxBlock(text, checked),
      _ => throw const FormatException('Unexpected JSON format'),
    };
  }
}

Kata kunci sealed adalah pengubah class yang artinya Anda hanya dapat memperluas atau menerapkan class ini di library yang sama. Karena sudah mengetahui subjenis class ini, penganalisis akan melaporkan error jika switch gagal mencakup salah satunya dan tidak menyeluruh.

Menggunakan ekspresi switch untuk menampilkan widget

  1. Update class BlockWidget di main.dart dengan ekspresi switch yang menggunakan pola objek untuk setiap kasus:

lib/main.dart

class BlockWidget extends StatelessWidget {
  final Block block;

  const BlockWidget({
    required this.block,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(8),
      child: switch (block) {
        HeaderBlock(:var text) => Text(
          text,
          style: Theme.of(context).textTheme.displayMedium,
        ),
        ParagraphBlock(:var text) => Text(text),
        CheckboxBlock(:var text, :var isChecked) => Row(
          children: [
            Checkbox(value: isChecked, onChanged: (_) {}),
            Text(text),
          ],
        ),
      },
    );
  }
}

Di versi pertama BlockWidget, Anda mengeksekusi switch di kolom objek Block untuk menampilkan TextStyle. Sekarang Anda mengeksekusi switch pada instance objek Block itu sendiri dan mencocokkan dengan pola objek yang mewakili subclass-nya, yang mengekstrak properti objek dalam prosesnya.

Penganalisis Dart dapat memeriksa bahwa setiap subclass ditangani di ekspresi switch karena Anda membuat Block sebagai class terkunci.

Perhatikan juga bahwa menggunakan ekspresi switch di sini akan memungkinkan Anda meneruskan hasil langsung ke elemen child, dibandingkan pernyataan return terpisah yang diperlukan sebelumnya.

  1. Lakukan hot reload untuk melihat kotak centang data JSON dirender untuk pertama kalinya:

Screenshot aplikasi yang menampilkan kotak centang 'Learn Dart 3'

13. Selamat

Anda telah berhasil bereksperimen dengan pola, kumpulan data, switch dan kasus yang ditingkatkan, serta class terkunci. Anda telah mendapatkan berbagai pengetahuan—tetapi masih banyak hal lain tentang fitur-fitur ini yang perlu dipelajari. Untuk mengetahui informasi selengkapnya tentang pola, buka spesifikasi fitur.

Berbagai jenis pola, berbagai konteks kemunculannya, dan potensi subpola bertingkat membuat kemungkinan dalam hal perilaku seperti tidak ada batasnya. Namun, itu semua terlihat dengan jelas.

Anda dapat memikirkan berbagai cara untuk menampilkan konten di Flutter menggunakan pola. Dengan menggunakan pola, Anda dapat mengekstrak data secara aman untuk membangun UI dalam beberapa baris kode.

Apa selanjutnya?

  • Lihat dokumentasi tentang pola, kumpulan data, switch dan kasus yang ditingkatkan, serta pengubah class di bagian Bahasa dalam dokumentasi Dart.

Dokumen referensi

Lihat contoh lengkap di repositori.

Guna mengetahui spesifikasi mendetail untuk setiap fitur baru, lihat dokumen desain asli: