ورود و عضویت
0
سبد خرید شما خالی است
0
سبد خرید شما خالی است

آموزش کار با پکیج Freezed در فلاتر

0 دیدگاه
5 دقیقه برای مطالعه

اگر با زبان هایی مثل کاتلین در طراحی اپلیکیشن های اندرویدی کار کرده باشید شاهد ویژگی به نام data classes و sealed classes بوده اید. متاسفانه در زبان برنامه نویسی دارت و فریمورک فلاتر این ویژگی وجود ندارد.

برای رفع این کمبود پکیج بسیار مفیدی به نام freezed توسط توسعه دهندگان ایجاد شده است که قصد بررسی آن را در این آموزش داریم.

کلاس های تغییر ناپذیر یا immutable

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

ابتدا یک کلاس مدل به نام Product ایجاد میکنیم. کدهای این کلاس بصورت زیر میباشد:

@immutable
class Product {
  final String _id;
  String get id => _id;

  final String _name;
  String get name => _name;

  final Color _color;
  Color get color => _color;

  const Product({
    required String id,
    required String name,
    Color color = Colors.red,
  })  : _id = id,
        _name = name,
        _color = color,
        assert(id != null),
        assert(name != null);

  Product copyWith({
    String id,
    String name,
    Color color,
  }) =>
      Product(id: id ?? _id, name: name ?? _name, color: color ?? _color);

  @override
  bool operator ==(Object other) =>
      other is Product &&
      other._id == _id &&
      other._name == _name &&
      other._color == _color;

  @override
  int get hashCode => hashValues(_id, _name, _color);
}

در ادامه صفحه ای به نام FreezedExamplePage را میسازیم. در این صفحه کدهای رابط کاربری برنامه را قرار میدهیم که به شکل ساده ای اطلاعات یک نمونه از کلاس Product را نمایش میدهد.

class FreezedExamplePage extends StatefulWidget {
  @override
  _FreezedExamplePageState createState() => _FreezedExamplePageState();
}

class _FreezedExamplePageState extends State<FreezedExamplePage> {
  Random _random;
  Product _product;

  @override
  void initState() {
    super.initState();
    _product = Product(id: "1", name: "iPhone 12");
    _random = Random();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Immutability and Equality"),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.refresh),
        onPressed: _randomProductData,
      ),
      body: ListTile(
        leading: CircleAvatar(
          backgroundColor: _product.color,
        ),
        title: Text(
          _product.name,
        ),
        subtitle: Text(
          _product.id,
        ),
      ),
    );
  }

  void _randomProductData() {
    final randomNumber = _random.nextInt(12);
    setState(
      () => _product = _product.copyWith(
        name: "iPhone $randomNumber",
      ),
    );
  }
}

سپس در فایل main.dart صفحه مورد نظر را قرار میدهیم تا با اجرا شدن پروژه فلاتر این صفحه نمایش داده شود.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: FreezedExamplePage(),
    );
  }
}

صفحه طراحی شده به شکل زیر میباشد که با هربار کلیک بروی دکمه شناور یک محصول جدید بصورت “iPhone $randomNumber” ایجاد میشود.

زبان دارت

در ادامه این مثال را به شکل دیگری پیاده سازی خواهیم کرد.

استفاده از Cache  در فلاتر

شروع کار با Freezed

ابتدا یک پروژه جدید ایجاد کرده و وارد فایل pubspec.yaml شوید. در این فایل پکیج های زیر را به تنظیمات برنامه اضافه کنید.

dependencies:
  flutter:
    sdk: flutter

  freezed_annotation: ^0.12.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  build_runner:
  freezed: ^0.12.2

به دلیل اینکه نیاز هست تا یک سری از کدها و کلاس ها به شکل خودکار ایجاد شوند پکیج build_runner را نیز به برنامه اضافه کرده ایم.

پیش تر در آموزش دیتابیس Hive از دستورات build_runner استفاده کرده بودیم.

بعد از نصب کتابخانه های موردنیاز کلاس product را به شکل زیر بازنویسی میکنیم.

import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'product.freezed.dart';

@freezed
abstract class Product with _$Product {
  const factory Product({
    String id,
    String name,
    Color color,
  }) = _Product;
}

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

ابتدا دستور @freezed و part ‘product.freezed.dart’ را به ابتدای کلاس اضافه کرده ایم که مشخص میکند این کلاس نیاز به تولید کدهای کمکی دارد که همان فایل product.freezed.dart میباشد. هر زمانی که نیاز دارید تا کدهایی توسط freezed تولید شوند به این دو دستور احتیاج دارید.

در ادامه کلاس مورد نظر را از نوع abstract تعریف میکنیم و با دستور with از کلاس $Product ارث بری میکند.

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

در بخش ترمینال پروژه دستور زیر را اضافه کنید تا کدهای موردنظر ایجاد شوند.

flutter pub run build_runner build

متغیرهای تغییرناپذیر

اگر برنامه را مجددا اجرا کنید مشاهده میکنید از بخش زیر برنامه دچار خطا میشود.

کلاس immutable  فلاتر

علت خطای مورد نظر این باشد که کلاس Product در حال حاضر به شکل پیش فرض غیرقابل تغییر یا immutable میباشد. ویژگی هایی که این کلاس دارد فقط شامل getter میباشد و هیچ متد setter در کلاس وجود ندارد تا محتویات جدید را ثبت کند.

برای تغییر اطلاعات باید به شکل غیر مستقیم عمل کنیم و این کار به وسیله متد copyWith انجام میشود.

_product = Product(id: "1", name: "iPhone 12");

final _newProduct = _product.copyWith(
  name: "My new name",
);

همچنین میتوانیم یک مقدار پیش فرض برای ویژگی Color  نیز قرار دهیم.

@freezed
abstract class Product with _$Product {
  const factory Product({
    String id,
    String name,
    @Default(Colors.red) Color color,
  }) = _Product;
}

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

@freezed
abstract class Product with _$Product {
  const factory Product({
    required String id,
    required String name,
    @Default(Colors.red) Color color,
  }) = _Product;
}

اگر به همین شکل کدهای بالا را اجرا کنیم برنامه دچار خطا میشود به دلیل اینکه ویژگی name مقداردهی نشده است.

دلیل این اتفاق این است که پکیج freezed برای مقادیر required و @Default دستور assert(x != null) را اضافه میکند.

برای مثال بیاید دستوری اضافه کنیم که نام محصول نتواند iPhone 13 باشد.

@freezed
abstract class Product with _$Product {
  @Assert("name != 'iPhone 13', 'iPhone 13 has yet to be released!'")
  const factory Product({
    required String id,
    required String name,
    @Default(Colors.red) Color color,
  }) = _Product;
}

این کار به راحتی توسط قابلیت @Assert انجام شد. در حال حاضر اگر نام محصول را iPhone 13 قرار دهید با خطای زیر مواجه خواهید شد.

کلاس data

همانطور که در تصویر مشخص است همان متنی که برای خطا در نظر گرفته بودیم نمایش داده میشود.

کلاس های union

یکی از ویژگی های مفید و جالب پکیج freezed امکان استفاده از کلاس های union میباشد.

در حال حاضر ما کلاسی در اختیار داریم به نام Product که شامل گوشی موبایل بود. در ادامه نیاز داریم که محصول جدیدی نیز داشته باشیم. در حالت باید یک کلاس دیگر از ابتدا تعریف کنیم. اما با استفاده از کلاس های union فقط به تعریف متد سازنده فکتوری جدید میپردازیم. به مثال زیر توجه کنید.

@freezed
abstract class Product with _$Product {
  const Product._();

  @Assert("name != 'iPhone 13', 'iPhone 13 has yet to be released!'")
  const factory Product.phone({
    required String id,
    required String name,
    @Default(Colors.red) Color color,
  }) = _Phone;

  const factory Product.insurance({
    required String id,
    required String name,
    required double quote,
  }) = _Insurance;

  @override
  String toString() {
    return "Product ID = $id, Name = $name";
  }
}

بدون نیاز به ساخت کلاسی دیگر در حال حاضر یک نوع محصول جدید داریم که البته هر دو محصول از نوع کلاس Product هستند. بنابراین میتوانیم آنها را در یک لیست ذخیره کنیم.

class _FreezedExamplePageState extends State<FreezedExamplePage> {
  List<Product> _productList;

  @override
  void initState() {
    super.initState();

    _productList = [
      Product.phone(id: "1", name: "iPhone 12"),
      Product.insurance(id: "2", name: "Home Insurance", quote: 25.44)
    ];

    _productList.forEach((Product product) {
      product.map(
          phone: (Product phone) => print("Phone!"),
          insurance: (Product insurance) =>
              print("Insurance!"));
    });
  }

  //
}

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

flutter: Phone!
flutter: Insurance!

دستور when

برای بررسی جملات شرطی از دستورات if, is و… در زبان دارت استفاده میکنیم. یکی از این موارد در معماری بلاک است که بررسی میکنیم چه stateایی در حال حاضر باید نمایش داده شود.

در پکیج freezed این کار و به راحتی با استفاده از دستور when میتوانیم انجام دهیم.

فرض کنید صفحه ما شامل چهار نوع state مختلف میباشد. کلاس مورد نظر را ابتدا به شکل زیر ایجاد میکنیم.

import 'package:freezed_annotation/freezed_annotation.dart';

part 'ui_state.freezed.dart';

@freezed
abstract class UIState with _$UIState {
  const factory UIState.initial() = Initial;
  const factory UIState.loading() = Loading;
  const factory UIState.success() = Success;
  const factory UIState.error(Failure failure) = Error;
}

حالا به شکل زیر برای حالت های مختلف محتویات مناسب را قرار میدهیم.

class _FreezedExamplePageState extends State<FreezedExamplePage> {
  UIState _uiState;

  @override
  void initState() {
    super.initState();
    _uiState = Initial();
  
  }

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text("Immutability and Equality"),
    ),
    body: _uiState.when(
      initial: _buildProductsLoading,
      loading: _buildProductsLoading,
      success: _buildProductsSuccess,
      error: _buildProductsError,
    ),
  );
}

}

بدین ترتیب نیازی به نوشتن جملات شرطی مختلف در متد build نخواهیم داشت.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

Hesam
07 فوریه 2022
آموزش فارسی فلاتر
آموزش فارسی flutter