1. مقدمة
يقدّم Dart 3 أنماطًا للغة، وهي فئة رئيسية جديدة من القواعد النحوية. بخلاف هذه الطريقة الجديدة لكتابة رمز Dart، هناك العديد من التحسينات الأخرى في اللغة، بما في ذلك
- السجلات لتجميع البيانات من أنواع مختلفة،
- معدِّلات الفئة للتحكم في الوصول،
- تبديل التعبيرات وعبارات if-case الجديدة.
وتوسِّع هذه الميزات الخيارات المتاحة لك عند كتابة رمز Dart. ستتعلم في هذا الدرس التطبيقي كيفية استخدامها لجعل التعليمة البرمجية أكثر إحكاما ومرونة ومرونة.
يفترض هذا الدرس التطبيقي حول الترميز أنّك تجيد استخدام Flutter وDart. إذا كنت تشعر بالصدأ قليلاً، ففكر في تحسين الأساسيات باستخدام الموارد التالية:
ما الذي ستقوم ببنائه
ينشئ هذا الدرس التطبيقي حول الترميز تطبيقًا يعرض مستند JSON في Flutter. يحاكي التطبيق ملف JSON من مصدر خارجي. يحتوي ملف JSON على بيانات المستند، مثل تاريخ التعديل والعنوان والعناوين والفقرات. ما عليك سوى كتابة رموز برمجية لتجميع البيانات بدقة في سجلّات كي يتم نقلها وتفريغها في أي مكان تحتاج فيه تطبيقات Flutter المصغّرة.
يمكنك بعد ذلك استخدام الأنماط لإنشاء الأداة المناسبة عندما تتطابق القيمة مع هذا النمط. سترى أيضًا كيفية استخدام الأنماط لإتلاف البيانات إلى متغيرات محلية.
المعلومات التي ستطّلع عليها
- كيفية إنشاء سجل يخزن قيمًا متعددة بأنواع مختلفة.
- كيفية عرض قيم متعددة من دالة باستخدام سجل.
- كيفية استخدام الأنماط لمطابقة البيانات والتحقق من صحتها وإتلافها من السجلات والكائنات الأخرى.
- كيفية ربط القيم المتطابقة للنمط بالمتغيّرات الحالية أو الجديدة.
- كيفية استخدام الإمكانات الجديدة لبيان التبديل، وتعبيرات التبديل، وعبارات الحالة الشرطية.
- كيفية الاستفادة من عملية التحقّق الشاملة لضمان معالجة كل حالة في بيان التبديل أو تعبير مفتاح التبديل
2. إعداد البيئة
- ثبِّت Flutter SDK.
- إعداد محرِّر مثل Visual Studio Code (VS Code)
- اتّبِع خطوات إعداد النظام الأساسي لنظام أساسي مستهدَف واحد على الأقل (نظام التشغيل iOS أو Android أو الكمبيوتر المكتبي أو متصفّح ويب).
3- إنشاء المشروع
قبل الاطّلاع على الأنماط والسجلات وغيرها من الميزات الجديدة، فكِّر في إنشاء مشروع بسيط على Flutter لكتابة جميع الرموز البرمجية له.
إنشاء مشروع Flutter
- استخدِم الأمر
flutter create
لإنشاء مشروع جديد باسمpatterns_codelab
. تمنع العلامة--empty
إنشاء تطبيق العدّاد العادي في ملفlib/main.dart
، وعليك إزالته على أي حال.
flutter create --empty patterns_codelab
- بعد ذلك، افتح الدليل
patterns_codelab
باستخدام رمز VS.
code patterns_codelab
ضبط الحدّ الأدنى لإصدار حزمة تطوير البرامج (SDK)
- يمكنك ضبط قيد إصدار حزمة تطوير البرامج (SDK) لمشروعك للاعتماد على الإصدار 3 من حزمة تطوير البرامج (SDK) أو الإصدارات الأحدث.
pubspec.yaml
environment:
sdk: ^3.0.0
4. إعداد المشروع
في هذه الخطوة، يمكنك إنشاء ملفين في 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.
- لإنشاء نقطة بداية للتطبيق، استبدِل محتوى
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(useMaterial3: true),
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
على إعداد أحدث إصدار من Material Design لتخصيص واجهة المستخدم. - يوفر
DocumentScreen
التنسيق المرئي للصفحة باستخدام تطبيقScaffold
المصغّر.
- للتأكّد من سير الأمور بسلاسة، شغِّل التطبيق على الجهاز المضيف بالنقر على التشغيل وتصحيح الأخطاء:
- يختار Flutter تلقائيًا المنصة المستهدَفة المتاحة. لتغيير النظام الأساسي المستهدَف، اختَر المنصة الحالية في شريط الحالة:
من المفترض أن يظهر لك إطار فارغ مع العنصرَين title
وbody
المحدّدين في تطبيق DocumentScreen
المصغّر:
5- إنشاء السجلات وإرجاعها
في هذه الخطوة، يمكنك استخدام السجلات لإرجاع قيم متعددة من استدعاء دالة. بعد ذلك، عليك استدعاء هذه الدالة في التطبيق المصغّر "DocumentScreen
" للوصول إلى القيم وإظهارها في واجهة المستخدِم.
إنشاء سجلّ وإرجاعه
- في
data.dart
، أضِف طريقة دالة getter جديدة إلى فئة المستند المسماة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
.
تنشئ عبارة الإرجاع سجلاً جديدًا من خلال تضمين القيمتين بين قوسين، (title, modified: now)
.
الحقل الأول هو موضعي بدون اسم، والحقل الثاني باسم modified
.
الوصول إلى حقول السجلّ
- في التطبيق المصغّر
DocumentScreen
، يمكنك استدعاء طريقة getter (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: [
Center(
child: Text(
'Last modified ${metadataRecord.modified}', // And this one.
),
),
],
),
);
}
}
تُرجع طريقة getter metadata
سجلاً يتم تعيينه للمتغير المحلي metadataRecord
. السجلات هي طريقة سهلة وسهلة لإرجاع قيم متعددة من استدعاء دالة واحدة وتعيينها إلى متغير.
وللوصول إلى الحقول الفردية التي تم إنشاؤها في هذا السجل، يمكنك استخدام سجلات بناء جملة getter المضمن.
- للحصول على حقل موضعي (حقل بدون اسم، مثل
title
)، استخدِم القيمة$<num>
في السجلّ. يعرض هذا الإجراء الحقول غير المُسمّاة فقط. - لا تحتوي الحقول المُسمّاة مثل
modified
على قيمة الحصول على موضع، لذا يمكنك استخدام اسمها مباشرةً، مثلmetadataRecord.modified
.
لتحديد اسم دالة getter لحقل موضعي، ابدأ من $1
وتخطَّ الحقول المُسمّاة. على سبيل المثال:
var record = (named: 'v', 'y', named2: 'x', 'z');
print(record.$1); // prints y
print(record.$2); // prints z
- إعادة التحميل السريع لمعرفة قيم JSON المعروضة في التطبيق. تتم إعادة تحميل المكوّن الإضافي VS Code Dart سريعًا في كل مرة تحفظ فيها ملفًا.
يمكنك أن ترى أن كل حقل قد حافظ في الواقع على نوعه.
- تستخدم الطريقة
Text()
السلسلة كوسيطة أولى. - يمثّل الحقل
modified
حقل التاريخ والوقت، ويتم تحويله إلىString
باستخدام استكمال السلسلة.
الطريقة الأخرى الآمنة من النوع لإرجاع أنواع مختلفة من البيانات هي تحديد فئة أكثر تفصيلاً.
6- مطابقة الأنماط وإتلافها
يمكن للسجلات جمع أنواع مختلفة من البيانات بكفاءة وتمريرها بسهولة. يمكنك الآن تحسين الرمز البرمجي باستخدام الأنماط.
يمثل النمط هيكلاً يمكن أن تتخذه قيمة واحدة أو أكثر، مثل المخطط. تتم مقارنة الأنماط بالقيم الفعلية لتحديد ما إذا كانت متطابقة.
تؤدي بعض الأنماط، عند تطابقها، إلى إتلاف القيمة المطابقة من خلال سحب البيانات منها. تتيح لك عملية الإتلاف فك ضغط القيم من أحد العناصر لتخصيصها للمتغيرات المحلية، أو إجراء مطابقة إضافية لها.
إتلاف سجل إلى متغيرات محلية
- أعِد ضبط طريقة
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
),
body: Column(
children: [
Center(
child: Text(
'Last modified $modified', // Modify
),
),
],
),
);
}
}
يحتوي نمط السجل (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
بدلاً من ذلك.
- إعادة التحميل سريعًا للاطّلاع على النتيجة نفسها كما في الخطوة السابقة. السلوك هو نفسه تمامًا؛ لقد جعلت للتو التعليمة البرمجية أكثر إيجازًا.
7. استخدام الأنماط لاستخراج البيانات
في سياقات معينة، لا تتطابق الأنماط وتتلفها فحسب، بل يمكنها أيضًا اتخاذ قرار بشأن ما تفعله الرمز البرمجي، استنادًا إلى ما إذا كان النمط مطابقًا أم لا. وتُعرف هذه الأنماط باسم الأنماط القابلة للإلغاء.
نمط تعريف المتغير الذي استخدمته في الخطوة الأخيرة هو نمط لا يمكن التراجع عنه: يجب أن تتطابق القيمة مع النمط وإلا سيكون خطأ ولن تحدث التدمير. فكر في أي إعلان متغير أو تعيين؛ لا يمكنك تعيين قيمة لمتغير إذا لم تكن من النوع نفسه.
من ناحية أخرى، تُستخدم الأنماط القابلة للإلغاء في سياقات تدفق التحكم:
- ويتوقع عدم تطابق بعض القيم التي يقارنون بها.
- وهي يُقصد منها التأثير في تدفق عنصر التحكّم استنادًا إلى ما إذا كانت القيمة متطابقة أم لا.
- وهي لا تقاطع التنفيذ مع ظهور خطأ في حال عدم تطابقها، ويتم نقلها فقط إلى العبارة التالية.
- يمكنها إتلاف وربط المتغيّرات التي تكون قابلة للاستخدام فقط عند مطابقتها.
قراءة قيم 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 باستخدام نمط خريطة
باستخدام نمط يمكن التراجع عنه، يمكنك التحقّق من أنّ تنسيق JSON له البنية المتوقعة باستخدام نمط الخريطة.
- استبدل الإصدار السابق من
metadata
بهذا الرمز:
lib/data.dart
class Document {
final Map<String, Object?> _json;
Document() : _json = jsonDecode(documentJson);
(String, {DateTime modified}) get metadata {
if (_json // Modify from here...
case {
'metadata': {
'title': String title,
'modified': String localModified,
}
}) {
return (title, modified: DateTime.parse(localModified));
} else {
throw const FormatException('Unexpected JSON');
} // to here.
}
}
ويظهر لك هنا نوع جديد من عبارة if (تم تقديمها في إصدار Dart 3)، وهي عبارة if-case. لا يتم تنفيذ نص الحالة إلا إذا تطابُق نمط الحالة مع البيانات في _json
. تكمل هذه المطابقة عمليات التحقّق نفسها التي كتبتها في الإصدار الأول من metadata
للتحقّق من صحة ملف JSON الوارد. يتحقق هذا الرمز مما يلي:
_json
هو نوع خريطة.- يحتوي
_json
على مفتاحmetadata
. _json
ليس قيمة خالية._json['metadata']
هو أيضًا نوع خريطة.- يحتوي
_json['metadata']
على المفتاحينtitle
وmodified
. title
وlocalModified
هما سلسلتان وليستا قيمتين فارغتين.
وفي حال عدم تطابق القيمة، يتم رفض النمط (يرفض متابعة التنفيذ) والانتقال إلى عبارة else
. إذا نجحت المطابقة، يؤدي النمط إلى تدمير قيم title
وmodified
من الخريطة ويربطها بمتغيرات محلية جديدة.
للحصول على قائمة كاملة بالأنماط، راجِع الجدول في قسم الأنماط الخاص بمواصفات الميزة.
8. إعداد التطبيق للحصول على مزيد من الأنماط
حتى الآن، تتناول الجزء 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');
}
}
}
تستخدم الدالة الإنشائية للمصنع fromJson()
حالة الحالة نفسها مع نمط خريطة سبق أن رأيته.
لاحظ أن json
يطابق نمط الخريطة، على الرغم من أنه لا يتم احتساب أحد المفاتيح، checked
، في النمط. تتجاهل أنماط الخريطة أي إدخالات في كائن الخريطة لم يتم احتسابها بشكل صريح في النمط.
عرض قائمة بكائنات الحظر
- أضِف بعد ذلك دالة جديدة
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
.
لا يتناول هذا القسم أي ميزات ذات صلة بالأنماط لم يسبق لك تجربتها في هذا الدرس التطبيقي. في الخطوة التالية، تستعد لعرض عناصر القائمة في واجهة المستخدم الخاصة بك.
9. استخدام الأنماط لعرض المستند
لقد نجحت الآن في إتلاف بيانات JSON وإعادة إنشائها باستخدام عبارة 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
.
- تستخدم عبارة الحالة الأولى نمط سلسلة ثابتة. يتطابق النمط مع إذا كانت
block.type
تساوي القيمة الثابتةh1
. - تستخدم عبارة الحالة الثانية نمطًا منطقيًا أو نمطًا مع نمطين ثابتين لسلسلتين كنمطين فرعيين. يتطابق النمط إذا كان
block.type
يطابق أيًا من النمطين الفرعيينp
أوcheckbox
.
- الحالة الأخيرة هي نمط حرف بدل،
_
. تتطابق أحرف البدل في حالات المبدل مع أي شيء آخر. فهي تعمل بالطريقة نفسها التي تتّبعها فقراتdefault
، ولكن لا يزال مسموحًا باستخدامها في عبارات التبديل (لأنّها مُطوّلة بعض الشيء).
يمكن استخدام أنماط أحرف البدل حيثما يُسمح بالنمط، على سبيل المثال، بنمط تعريف متغيّر: var (title, _) = document.metadata;
في هذا السياق، لا يربط حرف البدل أي متغير. وتتجاهل الحقل الثاني.
في القسم التالي، يمكنك الاطّلاع على المزيد من ميزات التبديل بعد عرض عناصر Block
.
عرض محتوى المستند
يمكنك إنشاء متغيّر محلي يحتوي على قائمة عناصر Block
من خلال استدعاء getBlocks()
في طريقة build
للأداة DocumentScreen
.
- استبدِل طريقة
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])
التطبيق المصغّر BlockWidget
لكل عنصر في قائمة الوحدات الأساسية التي يتم عرضها باستخدام طريقة getBlocks()
.
- شغِّل التطبيق، ومن المفترض أن تظهر عمليات الحظر على الشاشة:
10. استخدام تعبيرات مفتاح التبديل
تضيف الأنماط الكثير من الإمكانات إلى switch
وcase
. لجعلها قابلة للاستخدام في المزيد من الأماكن، تحتوي Dart على تعابير تبديل. يمكن أن توفر سلسلة من الحالات قيمة مباشرة لتعيين متغير أو عبارة إرجاع.
تحويل جملة أمر التبديل إلى تعبير تبديل
يوفر محلل Dart أدوات مساعدة لمساعدتك في إجراء تغييرات على الرمز البرمجي الخاص بك.
- حرك المؤشر إلى عبارة أمر التبديل من القسم السابق.
- انقر على المصباح لعرض أدوات المساعدة المتاحة.
- اختَر أداة المساعدة التحويل إلى تعبير مفتاح التبديل.
يبدو الإصدار الجديد من هذا الرمز على النحو التالي:
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
ويستخدم =>
لفصل النمط عن نص الحالة. على عكس عبارات مفتاح التبديل، تُرجع تعبيرات التبديل قيمة ويمكن استخدامها في أي مكان يمكن فيه استخدام تعبير.
11. استخدام أنماط الكائنات
Dart هي لغة كائنية التوجيه، لذلك تنطبق الأنماط على جميع الكائنات. في هذه الخطوة، يتم تفعيل نمط الكائن وإتلاف خصائص العنصر لتحسين منطق عرض التاريخ في واجهة المستخدم.
استخراج السمات من أنماط العناصر
في هذا القسم، يمكنك تحسين طريقة عرض تاريخ آخر تعديل باستخدام الأنماط.
- إضافة طريقة
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.
تستخدم كل حالة من حالات تعبير مفتاح التحكّم نمط كائن يطابق من خلال استدعاء دالة getters على خصائص الكائن inDays
وisNegative
. تبدو البنية أنّها بصدد إنشاء كائن Duration، ولكنه في الواقع يصل إلى حقول في كائن difference
.
تستخدِم الحالات الثلاث الأولى أنماطًا فرعية ثابتة 0
و1
و-1
لمطابقة خاصية الكائن inDays
وعرض السلسلة المقابلة.
تتعامل آخر حالتَين مع فترات بعد اليوم وأمس وغد:
- إذا كانت السمة
isNegative
تتطابق مع نمط ثابت منطقيtrue
، ما يعني أنّ تاريخ التعديل كان في الماضي، يتم عرضها قبل أيام. - إذا لم ترصد هذه الحالة الفرق، يجب أن تكون المدة عددًا موجبًا من الأيام (لا حاجة إلى إثبات الملكية صراحةً باستخدام
isNegative: false
)، بحيث يكون تاريخ التعديل في المستقبل ويعرض أيام من الآن.
إضافة منطق التنسيق للأسابيع
- أضف حالتين جديدتين إلى دالة التنسيق لتحديد الفترات التي تزيد عن 7 أيام بحيث يمكن لواجهة المستخدم عرضها على أنها أسابيع:
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
بعد نمط حالة الأحرف. - ويمكن استخدامها في حالات if، وتبديل العبارات، وتبديل التعبيرات.
- وهي تضيف شرطًا إلى نمط فقط بعد مطابقته.
- وإذا تم تقييم عبارة guard على "خطأ"، يتم رفض النمط بأكمله، وينتقل التنفيذ إلى الحالة التالية.
إضافة التاريخ الذي تم تنسيقه حديثًا إلى واجهة المستخدم
- أخيرًا، عدِّل طريقة
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]);
},
),
),
],
),
);
}
}
- إعادة التحميل السريع للاطّلاع على التغييرات في تطبيقك:
12. اختيار صف لإجراء عملية تبديل شاملة
يُرجى العلم أنّه لم يتم استخدام حرف بدل أو حالة تلقائية في نهاية عملية التبديل الأخيرة. على الرغم من أنّه من المفيد دائمًا تضمين حالة للقيم التي قد لا تندرج ضمن القيم المسموح بها، ولكن لا بأس في مثال بسيط كهذا لأنّك تعرف الحالات التي حدّدتها التي تأخذ في الحسبان جميع القيم المحتمَلة التي يمكن أن تسجّلها inDays
.
عند التعامل مع كل حالة في عملية تبديل، يُطلق عليها عملية تبديل شاملة. على سبيل المثال، يكون تفعيل النوع bool
شاملاً إذا كان ذلك يشمل حالات لكل من true
وfalse
. يُعدّ تفعيل النوع enum
شاملاً عندما تكون هناك حالات لكل قيمة من قيم التعداد أيضًا، لأنّ التعدادات تمثّل عددًا ثابتًا من القيم الثابتة.
وسّع Dart 3 التحقق من الشمولية ليشمل الكائنات والتسلسلات الهرمية للفئات باستخدام معدِّل الفئة الجديد sealed
. أعِد تصنيف فئة Block
كفئة متميّزة مختومة.
إنشاء الفئات الفرعية
- في "
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-to على أنّها تعبير تبديل يعرض الفئة الفرعية المقابلة للسمة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
هي معدِّل الفئة، ما يعني أنّه يمكنك توسيع نطاق هذه الفئة أو تنفيذها في المكتبة نفسها فقط. ونظرًا لأن المحلل يعرف الأنواع الفرعية لهذه الفئة، فإنه يبلغ عن خطأ إذا فشل التبديل في تغطية أحدها ولا يكون شاملاً.
استخدام تعبير مفتاح تحكّم لعرض التطبيقات المصغّرة
- عدِّل فئة 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 التحقق من التعامل مع كل فئة فرعية في تعبير مفتاح التبديل لأنك جعلت Block
فئة مختومة.
تجدر الإشارة أيضًا إلى أنّ استخدام تعبير Switch هنا يتيح لك تمرير النتيجة مباشرةً إلى العنصر child
، بدلاً من عبارة الإرجاع المنفصلة التي كانت مطلوبة في السابق.
- يجب إعادة تحميل الصفحة سريعًا للاطّلاع على بيانات JSON التي تم عرضها في مربّع الاختيار للمرة الأولى:
13. تهانينا
لقد جرَّبت بنجاح الأنماط والسجلات والتبديل المحسَّن لحالة الأحرف والفئات المغلقة. لقد تناولت الكثير من المعلومات - لكنك بالكاد اخدشت سطح هذه الميزات. لمزيد من المعلومات حول الأنماط، يمكنك الاطّلاع على مواصفات الميزات.
فأنواع الأنماط المختلفة، والسياقات المختلفة التي يمكن أن تظهر فيها، والدمج المحتمل للأنماط الفرعية، تجعل الاحتمالات في السلوك لا حصر لها. ولكن من السهل رؤيتها.
يمكنك تخيل جميع أنواع الطرق لعرض المحتوى في Flutter باستخدام الأنماط. باستخدام الأنماط، يمكنك استخراج البيانات بأمان لإنشاء واجهة المستخدم الخاصة بك في بضعة أسطر من التعليمات البرمجية.
الخطوات التالية
- يمكنك مراجعة الوثائق المتعلقة بالأنماط والسجلات والتبديل المحسّن والحالات ومعدِّلات الفئات في قسم اللغة في مستندات Dart.
المستندات المرجعية
يمكنك الاطّلاع على النموذج الكامل للرمز خطوة بخطوة في مستودع flutter/codelabs
.
للحصول على مواصفات متعمقة لكل ميزة جديدة، اطلع على مستندات التصميم الأصلية: