فلاتر

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

اگر با زبان هایی مثل کاتلین در طراحی اپلیکیشن های اندرویدی کار کرده باشید شاهد ویژگی به نام 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

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

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

علت خطای مورد نظر این باشد که کلاس 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 قرار دهید با خطای زیر مواجه خواهید شد.

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

کلاس های 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

Recent Posts

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

گیتهاب اکشن GitHub Actions یکی از ابزارهای گیتهاب است که به شما کمک می‌کنه تا…

2 روز ago

آموزش افزایش سرعت اجرای وب اپلیکیشن های فلاتر

اگر یک برنامه نویس فلاتر هستید و با از نسخه وب اپلیکیشن پروژتون استفاده میکنید…

4 هفته ago

آموزش جامع انتشار اپلیکیشن اندروید و فلاتر در فروشگاه گوگل پلی Google play

به عنوان یک برنامه نویس فلاتر یا اندروید بعد از اتمام پروسه طراحی اپلیکیشن نیاز…

2 ماه ago

دانلود سورس کد رابط کاربری اپلیکیشن فلاتر پروژه پادکست

طراحی رابط کاربری اپلیکیشن پادکست خود را با استفاده از این کیت توسعه UI/UX فلاتر…

2 ماه ago

فایربیس چیست؟ معرفی سرویس ابری Firebase و کاربردهای آن

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

3 ماه ago

آموزش پیاده سازی Method Channel در فلاتر + فیلم

فلاتر یک فریم ورک برنامه نویسی چندسکویی است که این امکان را برای برنامه نویس…

3 ماه ago