در الگوها و رکوردهای دارت شیرجه بزنید

۱. مقدمه

دارت ۳ الگوها را به زبان معرفی می‌کند، یک دسته بندی جدید و مهم از دستور زبان. فراتر از این روش جدید برای نوشتن کد دارت، چندین پیشرفت دیگر در زبان وجود دارد، از جمله

  • رکوردهایی برای دسته‌بندی داده‌های انواع مختلف،
  • اصلاح‌کننده‌های کلاس برای کنترل دسترسی، و
  • عبارات سوئیچ جدید و دستورات if-case .

این ویژگی‌ها، انتخاب‌های شما را هنگام نوشتن کد دارت گسترش می‌دهند. در این آزمایشگاه کد، یاد می‌گیرید که چگونه از آنها برای فشرده‌تر، ساده‌تر و انعطاف‌پذیرتر کردن کد خود استفاده کنید.

این آزمایشگاه کد فرض می‌کند که شما با Flutter و Dart آشنایی دارید. اگر کمی احساس ناآشنایی می‌کنید، می‌توانید با منابع زیر اصول اولیه را مرور کنید:

آنچه خواهید ساخت

این آزمایشگاه کد، برنامه‌ای ایجاد می‌کند که یک سند JSON را در فلاتر نمایش می‌دهد. این برنامه، JSON دریافتی از یک منبع خارجی را شبیه‌سازی می‌کند. JSON شامل داده‌های سند مانند تاریخ تغییر، عنوان، سرصفحه‌ها و پاراگراف‌ها است. شما کدی می‌نویسید که داده‌ها را به طور مرتب در رکوردها بسته‌بندی می‌کند تا بتوان آنها را به هر جایی که ویجت‌های فلاتر شما به آن نیاز دارند، منتقل و باز کرد.

سپس از الگوها برای ساخت ویجت مناسب، زمانی که مقدار با آن الگو مطابقت دارد، استفاده می‌کنید. همچنین خواهید دید که چگونه از الگوها برای تجزیه داده‌ها به متغیرهای محلی استفاده کنید.

برنامه نهایی که در این آزمایشگاه کد می‌سازید، سندی با عنوان، تاریخ آخرین تغییر، سرصفحه‌ها و پاراگراف‌ها است.

آنچه یاد خواهید گرفت

  • چگونه یک رکورد ایجاد کنیم که چندین مقدار با انواع مختلف را ذخیره کند.
  • نحوه برگرداندن چندین مقدار از یک تابع با استفاده از یک رکورد.
  • نحوه استفاده از الگوها برای تطبیق، اعتبارسنجی و تجزیه داده‌ها از رکوردها و سایر اشیاء.
  • نحوه اتصال مقادیر منطبق با الگو به متغیرهای جدید یا موجود.
  • نحوه استفاده از قابلیت‌های جدید دستور switch، عبارات switch و دستورات if-case.
  • چگونه می‌توان از بررسی جامعیت (exhaustiveness checking) بهره برد تا اطمینان حاصل شود که هر مورد (case) در یک دستور switch یا عبارت switch مدیریت می‌شود.

۲. محیط خود را آماده کنید

  1. SDK فلاتر را نصب کنید.
  2. یک ویرایشگر مانند Visual Studio Code (VS Code) تنظیم کنید .
  3. مراحل راه‌اندازی پلتفرم را برای حداقل یک پلتفرم هدف (iOS، اندروید، دسکتاپ یا مرورگر وب) طی کنید.

۳. پروژه را ایجاد کنید

قبل از اینکه به سراغ الگوها، رکوردها و سایر ویژگی‌های جدید برویم، لحظه‌ای را صرف ایجاد یک پروژه فلاتر کنید که تمام کدهای خود را برای آن بنویسید.

ایجاد یک پروژه فلاتر

  1. از دستور flutter create برای ایجاد یک پروژه جدید با نام patterns_codelab استفاده کنید. فلگ --empty از ایجاد برنامه استاندارد شمارنده در فایل lib/main.dart جلوگیری می‌کند، که در هر صورت باید آن را حذف کنید.
flutter create --empty patterns_codelab
  1. سپس، دایرکتوری patterns_codelab را با استفاده از VS Code باز کنید.
code patterns_codelab

VS Code پروژه ایجاد شده را نمایش می‌دهد

حداقل نسخه SDK را تنظیم کنید

  • محدودیت نسخه SDK را برای پروژه خود تنظیم کنید تا به Dart 3 یا بالاتر وابسته باشد.

pubspec.yaml

environment:
  sdk: ^3.0.0

۴. راه‌اندازی پروژه

در این مرحله، دو فایل Dart ایجاد یا به‌روزرسانی می‌کنید:

  • فایل main.dart که شامل ویجت‌های برنامه است، و
  • فایل data.dart که داده‌های برنامه را فراهم می‌کند.

شما در مراحل بعدی به اصلاح هر دوی این فایل‌ها ادامه خواهید داد.

تعریف داده‌ها برای برنامه

  • یک فایل جدید به lib/data.dart ایجاد کنید و کد زیر را به آن اضافه کنید:

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"
    }
  ]
}
''';

تصور کنید برنامه‌ای داده‌ها را از یک منبع خارجی، مانند یک جریان ورودی/خروجی یا درخواست HTTP، دریافت می‌کند. در این آزمایشگاه کد، شما با شبیه‌سازی داده‌های JSON ورودی با یک رشته چندخطی در متغیر documentJson ، این مورد استفاده واقع‌بینانه‌تر را ساده می‌کنید.

داده‌های JSON در کلاس Document تعریف شده‌اند. بعداً در این آزمایشگاه کد، توابعی را اضافه می‌کنید که داده‌ها را از JSON تجزیه‌شده برمی‌گردانند. این کلاس فیلد _json را در سازنده خود تعریف و مقداردهی اولیه می‌کند.

برنامه را اجرا کنید

دستور flutter create فایل lib/main.dart را به عنوان بخشی از ساختار پیش‌فرض فایل Flutter ایجاد می‌کند.

  1. برای ایجاد یک نقطه شروع برای برنامه، محتویات main.dart را با کد زیر جایگزین کنید:

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(),
      home: DocumentScreen(document: Document()),
    );
  }
}

class DocumentScreen extends StatelessWidget {
  final Document document;

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

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

شما دو ویجت زیر را به برنامه اضافه کردید:

  • DocumentApp آخرین نسخه از طراحی متریال را برای قالب‌بندی رابط کاربری تنظیم می‌کند.
  • DocumentScreen با استفاده از ویجت Scaffold طرح‌بندی بصری صفحه را ارائه می‌دهد.
  1. برای اطمینان از اینکه همه چیز به درستی کار می‌کند، برنامه را با کلیک روی Run and Debug روی دستگاه میزبان خود اجرا کنید:

دکمه‌ی «اجرا و اشکال‌زدایی»

  1. به طور پیش‌فرض، فلاتر هر پلتفرم هدف موجود را انتخاب می‌کند. برای تغییر پلتفرم هدف، پلتفرم فعلی را در نوار وضعیت انتخاب کنید:

انتخابگر پلتفرم هدف در VS Code

شما باید یک قاب خالی با عناصر title و body تعریف شده در ویجت DocumentScreen ببینید:

برنامه‌ای که در این مرحله ساخته شده است.

۵. ایجاد و بازگرداندن رکوردها

در این مرحله، شما از رکوردها برای بازگرداندن چندین مقدار از یک فراخوانی تابع استفاده می‌کنید. سپس، آن تابع را در ویجت DocumentScreen فراخوانی می‌کنید تا به مقادیر دسترسی پیدا کنید و آنها را در رابط کاربری منعکس کنید.

ایجاد و بازگرداندن یک رکورد

  • در data.dart ، یک متد getter جدید به کلاس Document با نام metadata اضافه کنید که یک رکورد برمی‌گرداند:

lib/data.dart

import 'dart:convert';

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

  (String, {DateTime modified}) get metadata {           // Add from here...
    const title = 'My Document';
    final now = DateTime.now();

    return (title, modified: now);
  }                                                      // to here.
}

نوع بازگشتی این تابع یک رکورد با دو فیلد است، یکی با نوع String و دیگری با نوع DateTime .

دستور return با قرار دادن دو مقدار در داخل پرانتز، یک رکورد جدید می‌سازد (title, modified: now) .

فیلد اول موقعیتی و بدون نام است و فیلد دوم modified نامگذاری شده است.

دسترسی به فیلدهای رکورد

  1. در ویجت DocumentScreen ، متد دریافت‌کننده‌ی metadata را در متد build فراخوانی کنید تا بتوانید رکورد خود را دریافت کرده و به مقادیر آن دسترسی داشته باشید:

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

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

  @override
  Widget build(BuildContext context) {
    final metadataRecord = document.metadata;              // Add this line.

    return Scaffold(
      appBar: AppBar(title: Text(metadataRecord.$1)),      // Modify this line,
      body: Column(
        children: [                                        // And the following line.
          Center(child: Text('Last modified ${metadataRecord.modified}')),
        ],
      ),
    );
  }
}

متد دریافت‌کننده‌ی metadata یک رکورد را برمی‌گرداند که به متغیر محلی metadataRecord اختصاص داده می‌شود. رکوردها روشی آسان و سبک برای بازگرداندن چندین مقدار از یک فراخوانی تابع و اختصاص آنها به یک متغیر هستند.

برای دسترسی به فیلدهای منفرد تشکیل شده در آن رکورد، می‌توانید از سینتکس getter داخلی رکوردها استفاده کنید.

  • برای دریافت یک فیلد موقعیتی (فیلدی بدون نام، مانند title )، از getter استفاده کنید. روی رکورد. این فقط فیلدهای بدون نام را برمی‌گرداند.
  • فیلدهای نامگذاری شده مانند modified getter موقعیتی ندارند، بنابراین می‌توانید مستقیماً از نام آن استفاده کنید، مانند metadataRecord.modified .

برای تعیین نام یک getter برای یک فیلد مکانی، از $1 شروع کنید و فیلدهای نام‌گذاری شده را نادیده بگیرید. برای مثال:

var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1);                               // prints y
print(record.$2);                               // prints z
  1. بارگذاری مجدد سریع (Hot reload) برای مشاهده مقادیر JSON نمایش داده شده در برنامه. افزونه VS Code Dart هر بار که فایلی را ذخیره می‌کنید، آن را به صورت خودکار بارگذاری مجدد می‌کند.

تصویری از برنامه که عنوان و تاریخ تغییر را نشان می‌دهد.

می‌توانید ببینید که در واقع هر فیلد نوع خود را حفظ کرده است.

  • متد Text() یک رشته (String) را به عنوان اولین آرگومان خود دریافت می‌کند.
  • فیلد modified یک DateTime است و با استفاده از درون‌یابی رشته‌ای به یک String تبدیل می‌شود.

روش دیگر برای بازگرداندن انواع مختلف داده که از نظر نوع داده ایمن است، تعریف یک کلاس است که طولانی‌تر است.

۶. الگوها را با هم تطبیق دهید و از هم جدا کنید

رکوردها می‌توانند انواع مختلف داده‌ها را به طور موثر جمع‌آوری کرده و به راحتی آنها را منتقل کنند. اکنون، کد خود را با استفاده از الگوها بهبود بخشید.

یک الگو، ساختاری را نشان می‌دهد که یک یا چند مقدار می‌تواند آن را بپذیرد، مانند یک طرح اولیه. الگوها با مقادیر واقعی مقایسه می‌شوند تا مشخص شود که آیا آنها مطابقت دارند یا خیر .

برخی از الگوها، وقتی مطابقت دارند، با بیرون کشیدن داده‌ها از مقدار تطبیق‌یافته، آن را تجزیه می‌کنند . تجزیه به شما امکان می‌دهد مقادیر را از یک شیء باز کنید تا آنها را به متغیرهای محلی اختصاص دهید یا تطبیق بیشتری روی آنها انجام دهید.

تجزیه یک رکورد به متغیرهای محلی

  1. متد build از DocumentScreen را برای فراخوانی metadata و استفاده از آن برای مقداردهی اولیه‌ی یک الگوی تعریف متغیر ، بازسازی کنید:

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

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

  @override
  Widget build(BuildContext context) {
    final (title, modified: modified) = document.metadata;   // Modify

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

الگوی رکورد (title, modified: modified) شامل دو الگوی متغیر است که با فیلدهای رکورد برگردانده شده توسط metadata مطابقت دارند.

  • این عبارت با زیرالگو مطابقت دارد زیرا نتیجه یک رکورد با دو فیلد است که یکی از آنها modified نام دارد.
  • از آنجا که آنها مطابقت دارند، الگوی اعلان متغیر، عبارت را تجزیه می‌کند، به مقادیر آن دسترسی پیدا می‌کند و آنها را به متغیرهای محلی جدید با همان نوع و نام، String title و DateTime modified ، مقید می‌کند.

یک روش خلاصه‌سازی برای زمانی وجود دارد که نام یک فیلد و متغیری که آن را پر می‌کند یکسان باشند. متد build مربوط به DocumentScreen را به صورت زیر بازنویسی کنید.

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

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

  @override
  Widget build(BuildContext context) {
    final (title, :modified) = document.metadata;            // Modify

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

سینتکس الگوی متغیر :modified خلاصه شده‌ی modified: modified است. اگر می‌خواهید یک متغیر محلی جدید با نام دیگری داشته باشید، می‌توانید به جای آن بنویسید modified: localModified .

  1. برای دیدن نتیجه مشابه مرحله قبل، از بارگذاری مجدد سریع (Hot reload) استفاده کنید. رفتار دقیقاً یکسان است؛ شما فقط کد خود را مختصرتر کرده‌اید.

۷. از الگوها برای استخراج داده‌ها استفاده کنید

در برخی زمینه‌ها، الگوها نه تنها تطبیق داده می‌شوند و از هم می‌پاشند، بلکه می‌توانند بر اساس اینکه آیا الگو تطبیق دارد یا خیر، در مورد عملکرد کد نیز تصمیم‌گیری کنند. به این الگوها، الگوهای قابل ابطال می‌گویند.

الگوی تعریف متغیری که در مرحله قبل استفاده کردید، یک الگوی غیرقابل انکار است: مقدار باید با الگو مطابقت داشته باشد، در غیر این صورت خطا رخ می‌دهد و تخریب اتفاق نمی‌افتد. به هر تعریف یا انتساب متغیری فکر کنید؛ اگر نوع متغیرها یکسان نباشد، نمی‌توانید مقداری را به آنها انتساب دهید.

از سوی دیگر، الگوهای ابطال‌پذیر در زمینه‌های جریان کنترل استفاده می‌شوند:

  • آنها انتظار دارند که برخی از مقادیری که با آنها مقایسه می‌کنند، با هم مطابقت نداشته باشند.
  • آنها برای تأثیرگذاری بر جریان کنترل ، بر اساس اینکه آیا مقدار مطابقت دارد یا خیر، در نظر گرفته شده‌اند.
  • اگر مطابقت نداشته باشند، اجرا را با خطا متوقف نمی‌کنند ، بلکه فقط به دستور بعدی می‌روند.
  • آنها می‌توانند متغیرهایی را که فقط در صورت تطابق قابل استفاده هستند، تجزیه و متصل کنند.

خواندن مقادیر JSON بدون الگو

در این بخش، داده‌ها را بدون تطبیق الگو می‌خوانید تا ببینید چگونه الگوها می‌توانند به شما در کار با داده‌های JSON کمک کنند.

  • نسخه قبلی metadata را با نسخه‌ای که مقادیر را از نگاشت _json می‌خواند، جایگزین کنید. این نسخه از metadata کپی کرده و در کلاس Document قرار دهید:

lib/data.dart

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

  (String, {DateTime modified}) get metadata {
    if (_json.containsKey('metadata')) {                     // Modify from here...
      final metadataJson = _json['metadata'];
      if (metadataJson is Map) {
        final title = metadataJson['title'] as String;
        final localModified = DateTime.parse(
          metadataJson['modified'] as String,
        );
        return (title, modified: localModified);
      }
    }
    throw const FormatException('Unexpected JSON');          // to here.
  }
}

این کد بدون استفاده از الگوها، صحت ساختار داده‌ها را تأیید می‌کند. در مرحله بعد، از تطبیق الگو برای انجام همان اعتبارسنجی با استفاده از کد کمتر استفاده می‌کنید. این کد قبل از انجام هر کار دیگری، سه بررسی انجام می‌دهد:

  • JSON شامل ساختار داده‌ای است که شما انتظار دارید: if (_json.containsKey('metadata'))
  • داده‌ها همان نوعی را دارند که انتظار دارید: if (metadataJson is Map)
  • اینکه داده‌ها تهی نیستند ، که به طور ضمنی در بررسی قبلی تأیید شده است.

خواندن مقادیر JSON با استفاده از الگوی map

با یک الگوی قابل ابطال، می‌توانید با استفاده از الگوی map تأیید کنید که JSON ساختار مورد انتظار را دارد.

  • نسخه قبلی metadata را با این کد جایگزین کنید:

lib/data.dart

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

  (String, {DateTime modified}) get metadata {
    if (_json case {                                         // Modify from here...
      'metadata': {'title': String title, 'modified': String localModified},
    }) {
      return (title, modified: DateTime.parse(localModified));
    } else {
      throw const FormatException('Unexpected JSON');
    }                                                        // to here.
  }
}

در اینجا، نوع جدیدی از دستور if (که در دارت ۳ معرفی شد) را می‌بینید، if-case . بدنه case فقط در صورتی اجرا می‌شود که الگوی case با داده‌های موجود در _json مطابقت داشته باشد. این تطابق همان بررسی‌هایی را که در نسخه اول metadata برای اعتبارسنجی JSON ورودی نوشتید، انجام می‌دهد. این کد موارد زیر را اعتبارسنجی می‌کند:

  • _json یک نوع Map است.
  • _json حاوی یک کلید metadata است.
  • _json تهی (null) نیست.
  • _json['metadata'] نیز از نوع Map است.
  • _json['metadata'] ‎ شامل کلیدهای title و modified .
  • title و localModified رشته هستند و تهی (null) نیستند.

اگر مقدار مطابقت نداشته باشد، الگو رد می‌کند (از ادامه اجرا خودداری می‌کند) و به بند else می‌رود. اگر مطابقت موفقیت‌آمیز باشد، الگو مقادیر title و modified را از نقشه تجزیه کرده و آنها را به متغیرهای محلی جدید متصل می‌کند.

برای فهرست کامل الگوها، به جدول موجود در بخش الگوها در مشخصات ویژگی مراجعه کنید.

۸. برنامه را برای الگوهای بیشتر آماده کنید

تا اینجا، شما به بخش metadata داده‌های JSON پرداختید. در این مرحله، منطق کسب‌وکار خود را کمی بیشتر اصلاح می‌کنید تا بتوانید داده‌های موجود در لیست blocks را مدیریت کرده و آن‌ها را در برنامه خود رندر کنید.

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

ایجاد کلاسی که داده‌ها را ذخیره می‌کند

  • یک کلاس جدید Block به data.dart اضافه کنید که برای خواندن و ذخیره داده‌های یکی از بلوک‌های موجود در داده‌های 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': final type, 'text': final text}) {
      return Block(type, text);
    } else {
      throw const FormatException('Unexpected JSON format');
    }
  }
}

سازنده‌ی factory fromJson() از همان if-case با الگوی map که قبلاً دیده‌اید، استفاده می‌کند.

خواهید دید که داده‌های JSON مشابه الگوی مورد انتظار به نظر می‌رسند، حتی با وجود اینکه یک قطعه اطلاعات اضافی به نام checked دارند که در الگو وجود ندارد. دلیل این امر این است که وقتی از این نوع الگوها (که "الگوهای نگاشت" نامیده می‌شوند) استفاده می‌کنید، آنها فقط به موارد خاصی که در الگو تعریف کرده‌اید اهمیت می‌دهند و هر چیز دیگری را در داده‌ها نادیده می‌گیرند.

لیستی از اشیاء Block را برگردانید

  • در مرحله بعد، یک تابع جدید به نام getBlocks() به کلاس Document اضافه کنید. getBlocks() فایل JSON را به نمونه‌هایی از کلاس Block تجزیه می‌کند و لیستی از بلوک‌ها را برای رندر در رابط کاربری شما برمی‌گرداند:

lib/data.dart

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

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

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

تابع getBlocks() لیستی از اشیاء Block را برمی‌گرداند که بعداً برای ساخت رابط کاربری از آنها استفاده می‌کنید. یک دستور if-case آشنا، اعتبارسنجی را انجام می‌دهد و مقدار فراداده‌های blocks را در یک List جدید به نام blocksJson تبدیل می‌کند (بدون الگوها، برای تبدیل به متد toList() نیاز دارید).

لیست تحت‌اللفظی شامل مجموعه‌ای برای پر کردن لیست جدید با اشیاء Block است.

این بخش هیچ ویژگی مرتبط با الگو را که قبلاً در این آزمایشگاه کد امتحان نکرده‌اید، معرفی نمی‌کند. در مرحله بعد، شما برای رندر کردن آیتم‌های لیست در رابط کاربری خود آماده می‌شوید.

۹. از الگوها برای نمایش سند استفاده کنید

اکنون شما با موفقیت داده‌های JSON خود را با استفاده از یک دستور if-case و الگوهای قابل ابطال، تجزیه و دوباره ترکیب می‌کنید. اما if-case تنها یکی از پیشرفت‌های ساختارهای جریان کنترل است که با الگوها همراه هستند. اکنون، شما دانش خود را در مورد الگوهای قابل ابطال برای تغییر دستورات به کار می‌برید.

کنترل آنچه رندر می‌شود با استفاده از الگوها با دستورات سوئیچ

  • در main.dart ، یک ویجت جدید به BlockWidget ایجاد کنید که استایل هر بلوک را بر اساس فیلد type آن تعیین می‌کند.

lib/main.dart

class BlockWidget extends StatelessWidget {
  final Block block;

  const BlockWidget({required this.block, super.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),
    );
  }
}

دستور switch در متد build فیلد type شیء block را فعال می‌کند.

  1. اولین دستور case از یک الگوی رشته‌ای ثابت استفاده می‌کند. این الگو در صورتی تطبیق می‌یابد که block.type برابر با مقدار ثابت h1 باشد.
  2. دستور case دوم از یک الگوی منطقی-or با دو الگوی رشته‌ای ثابت به عنوان زیرالگوهای خود استفاده می‌کند. الگو در صورتی مطابقت دارد که block.type با هر یک از زیرالگوهای p یا checkbox مطابقت داشته باشد.
  1. مورد آخر یک الگوی wildcard است، _ . wildcardها در switch caseها با هر چیز دیگری مطابقت دارند. آنها مانند عبارت‌های default رفتار می‌کنند، که هنوز در دستورات switch مجاز هستند (فقط کمی طولانی‌تر هستند).

الگوهای Wildcard می‌توانند در هر جایی که یک الگو مجاز است استفاده شوند - برای مثال، در الگوی اعلان متغیر: var (title, _) = document.metadata;

در این زمینه، کاراکترهای جایگزین هیچ متغیری را متصل نمی‌کنند. فیلد دوم را نادیده می‌گیرند.

در بخش بعدی، پس از نمایش اشیاء Block ، با ویژگی‌های بیشتر سوئیچ آشنا خواهید شد.

نمایش محتوای سند

با فراخوانی getBlocks() در متد build ویجت DocumentScreen ، یک متغیر محلی ایجاد کنید که شامل لیستی از اشیاء Block باشد.

  1. متد build موجود در DocumentationScreen را با این نسخه جایگزین کنید:

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

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

  @override
  Widget build(BuildContext context) {
    final (title, :modified) = document.metadata;
    final blocks = document.getBlocks();                           // Add this line

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

خط BlockWidget(block: blocks[index]) برای هر آیتم موجود در لیست بلوک‌های برگردانده شده از متد getBlocks() یک ویجت BlockWidget می‌سازد.

  1. برنامه را اجرا کنید، و سپس باید بلوک‌هایی را که روی صفحه نمایش داده می‌شوند، ببینید:

برنامه‌ای که محتوا را از بخش «بلوک‌ها» از داده‌های JSON نمایش می‌دهد.

۱۰. از عبارات سوئیچ استفاده کنید

الگوها قابلیت‌های زیادی به switch و case اضافه می‌کنند. برای اینکه بتوان از آن‌ها در جاهای بیشتری استفاده کرد، دارت عبارات switch را دارد. مجموعه‌ای از caseها می‌توانند مقداری را مستقیماً به یک متغیر انتساب دهند یا یک دستور return ارائه دهند.

دستور switch را به یک عبارت switch تبدیل کنید

تحلیلگر Dart به شما کمک می‌کند تا در کد خود تغییراتی ایجاد کنید.

  1. مکان‌نمای خود را به دستور switch از بخش قبل منتقل کنید.
  2. برای مشاهده‌ی کمک‌های موجود، روی لامپ کلیک کنید.
  3. گزینه‌ی «تبدیل به تغییر عبارت» را انتخاب کنید.

دستیار «عبارت تبدیل به سوئیچ» در VS Code موجود است.

نسخه جدید این کد به این شکل است:

lib/main.dart

class BlockWidget extends StatelessWidget {
  final Block block;

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

  @override
  Widget build(BuildContext context) {
    TextStyle? textStyle;                                          // Modify from here
    textStyle = switch (block.type) {
      'h1' => Theme.of(context).textTheme.displayMedium,
      'p' || 'checkbox' => Theme.of(context).textTheme.bodyMedium,
      _ => Theme.of(context).textTheme.bodySmall,
    };                                                             // to here.

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

یک عبارت سوئیچ شبیه به یک دستور سوئیچ به نظر می‌رسد، اما کلمه کلیدی case را حذف کرده و از => برای جدا کردن الگو از بدنه case استفاده می‌کند. برخلاف دستورات سوئیچ، عبارات سوئیچ یک مقدار را برمی‌گردانند و می‌توانند در هر جایی که یک عبارت قابل استفاده باشد، استفاده شوند.

۱۱. از الگوهای شیء استفاده کنید

دارت یک زبان شیءگرا است، بنابراین الگوها برای همه اشیاء اعمال می‌شوند. در این مرحله، شما یک الگوی شیء را فعال می‌کنید و ویژگی‌های شیء را تجزیه می‌کنید تا منطق رندر تاریخ رابط کاربری خود را بهبود بخشید.

استخراج ویژگی‌ها از الگوهای شیء

در این بخش، شما با استفاده از الگوها، نحوه نمایش آخرین تاریخ تغییر را بهبود می‌بخشید.

  • متد formatDate را به main.dart اضافه کنید:

lib/main.dart

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

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

این متد یک عبارت سوئیچ برمی‌گرداند که مقدار difference را فعال می‌کند، یک شیء Duration . این شیء نشان‌دهنده‌ی بازه زمانی بین today و مقدار modified از داده‌های JSON است.

هر مورد از عبارت سوئیچ از یک الگوی شیء استفاده می‌کند که با فراخوانی getterها روی ویژگی‌های شیء inDays و isNegative مطابقت دارد. این سینتکس به نظر می‌رسد که ممکن است در حال ساخت یک شیء Duration باشد، اما در واقع به فیلدهای شیء difference دسترسی پیدا می‌کند.

سه مورد اول از زیرالگوهای ثابت 0 ، 1 و -1 برای تطبیق ویژگی شیء inDays و بازگرداندن رشته مربوطه استفاده می‌کنند.

دو مورد آخر، مدت زمان‌هایی فراتر از امروز، دیروز و فردا را مدیریت می‌کنند:

  • اگر ویژگی isNegative با الگوی ثابت بولی true مطابقت داشته باشد، به این معنی که تاریخ اصلاح در گذشته بوده است، روزها پیش را نمایش می‌دهد.
  • اگر آن مورد تفاوت را نشان ندهد، آنگاه مدت زمان باید عدد مثبتی بر حسب روز باشد (نیازی به تأیید صریح با isNegative: false )، بنابراین تاریخ اصلاح در آینده است و روزهای بعد از اکنون را نمایش می‌دهد.

منطق قالب‌بندی را برای هفته‌ها اضافه کنید

  • دو مورد جدید به تابع قالب‌بندی خود اضافه کنید تا مدت زمان‌های طولانی‌تر از ۷ روز را شناسایی کنید تا رابط کاربری بتواند آنها را به صورت هفته نمایش دهد:

lib/main.dart

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

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

این کد بندهای محافظ را معرفی می‌کند:

  • یک بند محافظ از کلمه کلیدی when بعد از الگوی case استفاده می‌کند.
  • آنها می‌توانند در if-caseها، دستورات switch و عبارات switch استفاده شوند.
  • آنها فقط پس از تطبیق یک الگو، شرطی را به آن اضافه می‌کنند.
  • اگر بند محافظ به نادرست ارزیابی شود، کل الگو رد می‌شود و اجرا به حالت بعدی ادامه می‌یابد.

تاریخ تازه قالب‌بندی شده را به رابط کاربری اضافه کنید

  1. در نهایت، متد build در DocumentScreen را به‌روزرسانی کنید تا از تابع formatDate استفاده کند:

lib/main.dart

class DocumentScreen extends StatelessWidget {
  final Document document;

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

  @override
  Widget build(BuildContext context) {
    final (title, :modified) = document.metadata;
    final formattedModifiedDate = formatDate(modified);            // Add this line
    final blocks = document.getBlocks();

    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Column(
        children: [
          Text('Last modified: $formattedModifiedDate'),           // Modify this line
          Expanded(
            child: ListView.builder(
              itemCount: blocks.length,
              itemBuilder: (context, index) {
                return BlockWidget(block: blocks[index]);
              },
            ),
          ),
        ],
      ),
    );
  }
}
  1. برای مشاهده تغییرات در برنامه خود، از بارگذاری مجدد سریع (Hot reload) استفاده کنید:

برنامه‌ای که رشته‌ی «آخرین تغییر: ۲ هفته پیش» را با استفاده از تابع formatDate() نمایش می‌دهد.

۱۲. یک کلاس را برای تعویض کامل مهر و موم کنید

توجه داشته باشید که در انتهای آخرین سوئیچ از wildcard یا حالت پیش‌فرض استفاده نکرده‌اید. اگرچه بهتر است همیشه برای مقادیری که ممکن است از قلم بیفتند، یک حالت را نیز در نظر بگیرید، اما در یک مثال ساده مانند این اشکالی ندارد زیرا می‌دانید حالت‌هایی که تعریف کرده‌اید، تمام مقادیر ممکنی را که تابع inDays می‌تواند به طور بالقوه بگیرد، در نظر می‌گیرند.

وقتی هر مورد در یک سوئیچ مدیریت می‌شود، به آن سوئیچ جامع می‌گویند. برای مثال، فعال کردن یک نوع bool زمانی جامع است که مواردی برای true و false داشته باشد. فعال کردن یک نوع enum نیز زمانی جامع است که مواردی برای هر یک از مقادیر enum وجود داشته باشد، زیرا enumها تعداد ثابتی از مقادیر ثابت را نشان می‌دهند.

دارت ۳ با استفاده از اصلاح‌کننده‌ی کلاس جدید sealed بررسی جامعیت را به اشیاء و سلسله مراتب کلاس‌ها گسترش داد. کلاس Block خود را به عنوان یک سوپرکلاس sealed بازسازی کنید.

ایجاد زیرکلاس‌ها

  • در data.dart ، سه کلاس جدید ایجاد کنید HeaderBlock ، ParagraphBlock و CheckboxBlock - که از 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);
}

هر یک از این کلاس‌ها با مقادیر type متفاوتی از JSON اصلی مطابقت دارند: 'h1' ، 'p' و 'checkbox' .

سوپرکلاس را پلمپ کنید

  • کلاس Block را به عنوان sealed علامت‌گذاری کنید. سپس، if-case را به عنوان یک عبارت switch که زیرکلاس مربوط به type مشخص شده در 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'),
    };
  }
}

کلمه کلیدی sealed یک اصلاح‌کننده کلاس است، به این معنی که شما فقط می‌توانید این کلاس را در همان کتابخانه گسترش دهید یا پیاده‌سازی کنید . از آنجایی که تحلیلگر زیرنوع‌های این کلاس را می‌شناسد، اگر یک سوئیچ نتواند یکی از آنها را پوشش دهد و جامع نباشد، خطا گزارش می‌دهد.

برای نمایش ویجت‌ها از عبارت سوئیچ استفاده کنید

  1. کلاس BlockWidget را در main.dart با یک عبارت سوئیچ که از الگوهای شیء برای هر مورد استفاده می‌کند، به‌روزرسانی کنید:

lib/main.dart

class BlockWidget extends StatelessWidget {
  final Block block;

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

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

در اولین نسخه BlockWidget ، فیلدی از شیء Block را برای برگرداندن یک TextStyle فعال کردید. اکنون، نمونه‌ای از خود شیء Block را تغییر می‌دهید و آن را با الگوهای شیء که نشان‌دهنده زیرکلاس‌های آن هستند، مطابقت می‌دهید و در این فرآیند، ویژگی‌های شیء را استخراج می‌کنید.

تحلیلگر Dart می‌تواند بررسی کند که آیا هر زیرکلاس در عبارت switch مدیریت می‌شود یا خیر، زیرا شما Block به یک کلاس مهر و موم شده تبدیل کرده‌اید.

همچنین توجه داشته باشید که استفاده از عبارت سوئیچ در اینجا به شما امکان می‌دهد نتیجه را مستقیماً به عنصر child ارسال کنید، برخلاف دستور return جداگانه‌ای که قبلاً لازم بود.

  1. بارگذاری مجدد سریع برای دیدن داده‌های JSON مربوط به چک‌باکس که برای اولین بار رندر شده‌اند:

برنامه‌ای که گزینه «یادگیری دارت ۳» را نمایش می‌دهد

۱۳. تبریک

شما با موفقیت الگوها، رکوردها، سوئیچ و کیس بهبود یافته و کلاس‌های مهر و موم شده را آزمایش کردید. اطلاعات زیادی را پوشش دادید - اما فقط به طور سطحی به این ویژگی‌ها پرداختید. برای اطلاعات بیشتر در مورد الگوها، به مشخصات ویژگی مراجعه کنید.

انواع مختلف الگوها، زمینه‌های مختلفی که می‌توانند در آنها ظاهر شوند، و احتمال تودرتو بودن زیرالگوها، احتمالات موجود در رفتار را ظاهراً بی‌پایان می‌کند. اما دیدن آنها آسان است.

شما می‌توانید انواع روش‌های نمایش محتوا در فلاتر با استفاده از الگوها را تصور کنید. با استفاده از الگوها، می‌توانید با خیال راحت داده‌ها را استخراج کنید تا رابط کاربری خود را در چند خط کد بسازید.

بعدش چی؟

  • مستندات مربوط به الگوها، رکوردها، سوئیچ‌ها و کیس‌های بهبود یافته و اصلاح‌کننده‌های کلاس را در بخش زبان مستندات دارت بررسی کنید.

اسناد مرجع

کد نمونه کامل را گام به گام در مخزن flutter/codelabs مشاهده کنید.

برای مشخصات دقیق هر ویژگی جدید، اسناد طراحی اصلی را بررسی کنید: