فلٹر میں لیگو فن تعمیر کو کیسے نافذ کیا جائے۔ [Full Handbook]

تقریباً ہر ایک نے اپنی بالغ زندگی میں کسی ایک سیٹ کے بغیر دو LEGO اینٹوں کو ایک ساتھ جوڑا ہے۔ ایک اینٹ کو دوسری اینٹ پر دبائیں اور یہ جگہ پر کلک کرتا ہے۔

آپ نے شاید کبھی نہیں سوچا ہو گا کہ اینٹ کیسے بنتی ہے، کون سا پلاسٹک استعمال ہوتا ہے، یا یہ کس فیکٹری سے آئی ہے۔ اس وقت آپ کو صرف ایک چیز کی پرواہ تھی: کیا جڑیں آپس میں ملتی ہیں؟

وہ چھوٹے، عام لمحات اس ہینڈ بک کا پورا خیال ہیں۔ اب، ایک لمحے کے لیے LEGO سے دور رہیں اور اس کے بجائے ایک Flutter پروجیکٹ کا تصور کریں۔ اس پروجیکٹ میں کہیں ایک اسکرین ہے جسے ٹیم میں شامل ہر شخص خفیہ طور پر کھولنے سے ڈرتا ہے۔ ایک بڑے سسٹم کے اندر ڈیٹا درآمد کریں، فارمیٹ کریں، توثیق کریں اور رینڈر کریں۔ build() طریقہ

بات یہ ہے کہ یہ کام کرتا ہے۔ کوئی اسے چھونا نہیں چاہتا۔ ادائیگی کے بہاؤ کو تبدیل کرنے کا مطلب ہے کہ تین غیر متعلقہ مسائل کو اسکرول کرنا ہے تاکہ ایک لائن کو تلاش کیا جا سکے جس میں ترمیم کی ضرورت ہے۔

ان دو تجربات کے درمیان فرق (لیگو اینٹ پر کلک کرنے کا اطمینان بخش احساس اور ایک فائل کھولنے کا خوف) ایک عادت میں آتا ہے۔ LEGO اینٹوں کو اس لیے بنایا گیا ہے کہ آپ کو اندر کیا ہے، بس کنکشن پوائنٹس کے بارے میں کچھ جاننے کی ضرورت نہیں ہے۔ لیکن زیادہ تر کوڈ پہلے سے طے شدہ طور پر اس طرح نہیں لکھا جاتا ہے۔

"LEGO آرکیٹیکچر” صرف LEGO کے اینٹوں کو بنانے کے طریقے کو کوڈ کرنے کا فیصلہ ہے۔ اور یہ ہینڈ بک آپ کو آہستہ آہستہ کسی بہت چھوٹی چیز سے شروع کرنے کی عادت سکھائے گی جسے فن تعمیر کہا جاتا ہے اور اس پر ٹکڑے ٹکڑے کرکے اس پر تعمیر کرنا جب تک کہ آپ پوری ایپ کو ایک ساتھ نہیں رکھ سکتے۔

راستے میں، ہم کلین آرکیٹیکچر کو دیکھیں گے، جو ایک ہی عادات کو لاگو کرنے کا ایک مخصوص، معروف طریقہ ہے، اور دیکھیں گے کہ دونوں بالکل کہاں ملتے ہیں۔

انڈیکس

شرطیں

ابتدائی حصے میں آپ کو بنیادی فلٹر ویجٹ لکھنے اور فلٹر ایپس چلانے سے آشنا ہونا چاہیے۔ StatelessWidget اور عام ویجیٹ کنفیگریشن۔

آپ کو ڈارٹ کلاسز، کنسٹرکٹرز اور تجریدی کلاسز کو بھی سمجھنا چاہیے۔ کیونکہ معاہدے، وہ سٹڈز جن پر یہ پوری ہینڈ بک مبنی ہے، صرف تجریدی کلاسز اور انٹرفیس ہیں۔ انحصار انجیکشن یا سروس لوکیٹر کے بارے میں کچھ علم مددگار ہے، لیکن ضروری نہیں ہے۔ اس کی وجہ یہ ہے کہ خیال شروع سے متعارف کرایا جاتا ہے جب یہ پہلی بار سامنے آتا ہے۔

بعد کے حصوں میں flutter_bloc، get_it، dioاور go_router مثال کے طور پر پیکیج۔ تمام درآمدات کی وضاحت اسی لمحے کی جاتی ہے جب وہ ظاہر ہوتے ہیں، لہذا آپ کو انہیں پہلے استعمال کرنے کی ضرورت نہیں ہے۔

صاف فن تعمیر کیا حاصل کرنے کی کوشش کر رہا ہے اس کا عملی علم (فریم ورک سے آزاد کاروباری منطق کو برقرار رکھنا) مفید سیاق و سباق ہے، لیکن ہینڈ بک میں ان قارئین کے لیے کریش کورس بھی شامل ہے جو اس میں نئے ہیں۔

LEGO آرکیٹیکچر کو ایک ماڈیولر monorepo کے ساتھ ضم کرنے کا سیکشن اس خیال کو خالی فولڈر سے بناتا ہے، لہذا آپ کو monorepos کے بارے میں کسی پیشگی معلومات کی بھی ضرورت نہیں ہے۔ لیکن آپ وہاں پہنچنے سے پہلے، اگر آپ monorepo ڈھانچے، Melos، اور Dart ورک اسپیس کی مزید گہرائی سے، وقف شدہ واک تھرو چاہتے ہیں، تو پہلے فلٹر میں Monorepos کو کیسے استعمال کریں پڑھنا آپ کو کچھ مفید پس منظر فراہم کرے گا کہ ٹیمیں سب سے پہلے monorepos تک کیوں پہنچتی ہیں۔

"لیگو آرکیٹیکچر” کا واقعی کیا مطلب ہے۔

آئیے ایک لمحے کے لیے واپس LEGO bricks پر چلتے ہیں۔ کیونکہ یہاں دو چیزیں قابل توجہ ہیں۔ ایک اینٹ کیا ہے: اس کی شکل، اس کا رنگ، اس کا مقصد۔ اور سٹڈز، سب سے اوپر معیاری کنکشن پوائنٹس، اور نچلے حصے میں ٹیوبیں ہیں جنہیں اسی معیار کی پیروی کرنے والی دوسری اینٹوں سے جوڑا جا سکتا ہے۔

کسی کو یہ جاننے کی ضرورت نہیں کہ ایک اینٹ کو دوسری اینٹ سے جوڑنے کے لیے اس کی شکل کیسے بنی۔ آپ کو صرف مماثل جڑوں کی ضرورت ہے۔

یہ پورا خیال ہے، اور سافٹ ویئر اسے تقریباً بالکل کاپی کر سکتا ہے۔ ایک اینٹ ایک ایپ کی اکائی بن جاتی ہے، جو ایک ویجیٹ، کلاس، سروس، یا پوری خصوصیت ہو سکتی ہے۔ ایک جڑنا وہ معاہدہ بن جاتا ہے جس کے ذریعے ایک اینٹ کو بیرونی دنیا کے سامنے لایا جاتا ہے، اور کوڈ میں اس کا مطلب عام طور پر ایک تجریدی کلاس، انٹرفیس، یا اچھی طرح سے طے شدہ فنکشن دستخط ہوتا ہے۔

کوڈ میں دو اینٹوں کو ایک ساتھ جوڑنے کا مطلب یہ ہے کہ ایپ کا ایک حصہ دوسرے پر انحصار کرتا ہے صرف اپنے معاہدوں کے ذریعے اور اس تک رسائی یا انحصار نہیں کرتا ہے کہ دوسرا حصہ اس کے نیچے کیسے بنایا گیا ہے۔

یاد رکھیں کہ دونوں اینٹوں کا دنیا سے رابطہ صرف ان کے درمیان ہونے والے معاہدوں کے ذریعے ہوتا ہے۔ کسی کو یہ جاننے کی ضرورت نہیں کہ کنکریٹ کی اینٹیں دوسری طرف سے جڑی ہوئی ہیں۔ ٹھوس چیزوں کے بجائے معاہدے کرنے کی واحد عادت اس ہینڈ بک میں موجود ہر چیز کے پیچھے محرک ہے۔ آپ ان کا سامنا کرتے رہیں گے، پہلے ایک ویجیٹ میں، پھر ایک کلاس میں، پھر پوری خصوصیت میں، اور آخر میں پورے پیکیج میں۔

کوڈ ظاہر ہونے سے پہلے ایک اور چیز کو اندرونی بنانے کے قابل ہے۔ ایک اینٹ جو اپنا کام اچھی طرح کرتی ہے اسے پہلے دوسری فائلوں کو کھولے بغیر خود ہی بولنا چاہئے۔ آپ کو اپنے پڑوسیوں کے نوٹس کیے بغیر کنکشن کو تبدیل کرنے کے قابل ہونا چاہئے۔ اور آپ کو اپنے پڑوسیوں کو یہ نہیں دکھانا چاہئے کہ آپ کس طرح کچھ کرتے ہیں، اور آپ کو انہیں صرف یہ نہیں دکھانا چاہئے کہ آپ کیا کرتے ہیں۔

ان تینوں احساسات کو ذہن میں رکھیں۔ اب کی تمام مثالیں دراصل صرف تین جذبات ہیں جن کا اظہار ڈارٹ میں کیا گیا ہے۔

یہاں ایک راز ہے۔ آپ اس کا نام لیے بغیر اس کا ایک چھوٹا ورژن پہلے ہی کر چکے ہیں۔ اگلی سطر پر نظر ڈالیں، جو آپ نے یقیناً پہلے لکھی ہیں۔

Padding(
  padding: const EdgeInsets.all(8),
  child: const Text('Hello'),
)

Padding یہ بالکل ایک کام کرتا ہے اور واقعی اس کی پرواہ نہیں کرتا کہ آپ اسے کیا دیتے ہیں۔ child. ایسا ہو سکتا ہے۔ Text، Image، Columnیا کچھ اور۔ سادہ نظر میں چھپے ہوئے اینٹ اور جڑیں ہیں۔ Padding یہ ایک اینٹ ہے۔ کہ child پیرامیٹر سٹڈ ہے۔ کیونکہ کوئی بھی ویجیٹ جو اس بیان پر فٹ بیٹھتا ہے خوش آئند ہے۔ آپ نے کبھی نہیں سکھایا Padding متن یا تصاویر کو کیسے رینڈر کریں۔ جاننے کی بالکل ضرورت نہیں تھی۔

اب ان سب کو ایک ساتھ استعمال کرنے کی کوشش کریں، آپ کے سر میں فٹ ہونے کے لیے کافی چھوٹے، اور دیکھیں کہ اس عادت کے جانے کے وقت کیا ہوتا ہے۔ فرض کریں کہ آپ قیمتیں ظاہر کرنے کے لیے تھوڑا سا گول، سایہ دار باکس چاہتے ہیں۔

class PriceTag extends StatelessWidget {
  final double price;
  const PriceTag({super.key, required this.price});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(8),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(6),
      ),
      child: Text('$${price.toStringAsFixed(2)}'),
    );
  }
}

یہ ایک مکمل طور پر قابل قبول، بالکل چھوٹا ویجیٹ ہے، جس میں کوئی ٹوٹا ہوا پرزہ نہیں ہے۔ لیکن اس پر گہری نظر ڈالیں کہ یہ اصل میں کیا کرتا ہے۔ ایک ہی کلاس کے اندر، ہم بیک وقت دو غیر متعلقہ اشیاء کا تعین کر رہے ہیں: مواد کے ارد گرد باکس کی شکل اور خود مواد۔

جس لمحے آپ کو کسی چیز کے ارد گرد ایک ہی گول شیڈڈ باکس کی ضرورت ہوتی ہے جس کی قیمت نہیں ہوتی ہے (جیسے ایک چھوٹا سا لیبل جو "فروخت” کہتا ہے)، آپ پھنس گئے ہیں۔ تم ہو Container نئے ویجٹ کے ساتھ سجانے یا extends ہم سٹائل کی چھ سطروں کو دوبارہ استعمال کرنے کے لیے ایک چھوٹی کلاس کا درجہ بندی بنا کر شروع کرتے ہیں۔

دونوں کو مضبوطی سے جوڑا گیا ہے جس کے بارے میں یہ پوری ہینڈ بک آپ کو بتانے کی کوشش کر رہی ہے۔

اصلاح ایک ہی ہے۔ Padding میں پہلے ہی دکھا چکا ہوں۔ باکس کو خود ہی باہر آنے دیں تاکہ کوئی بھی بچہ اسے قبول کر سکے۔

class SurfaceCard extends StatelessWidget {
  final Widget child;
  const SurfaceCard({super.key, required this.child});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(8),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(6),
      ),
      child: child,
    );
  }
}

SurfaceCard اب میں صرف ایک بات جانتا ہوں۔ یہ طریقہ ایک چھوٹے، گول، سایہ دار باکس کی طرح لگتا ہے۔ اس میں ایک سٹڈ بھی ہے۔ childبالکل ایک ہی شکل Paddingایس۔ PriceTag چونکہ اب آپ کو یہ جاننے کی ضرورت نہیں ہے کہ باکس کیسے کھینچنا ہے، اس لیے یہ تقریباً کچھ بھی نہیں رہ گیا ہے۔

class PriceTag extends StatelessWidget {
  final double price;
  const PriceTag({super.key, required this.price});

  @override
  Widget build(BuildContext context) {
    return SurfaceCard(child: Text('$${price.toStringAsFixed(2)}'));
  }
}

وہ ایک تبدیلی اس حصے کا پورا سبق ہے۔ SurfaceCard اب آپ اسے اپنے "سیل” لیبل، چھوٹا اوتار، تعریفی بیج وغیرہ کے پیچھے رکھ سکتے ہیں اور اسے دوبارہ کبھی ہاتھ نہیں لگانا پڑے گا۔ کیونکہ انہوں نے کبھی یہ خیال رکھنا نہیں سیکھا کہ ان کا بچہ کیسا لگتا ہے۔

جانچ کرنا کہ آیا اس طرح کی اینٹ واقعی اچھی طرح سے بنائی گئی ہے یا نہیں کیا میں ایک لائن کو کاپی کیے بغیر اسے کسی نئی جگہ پر دوبارہ استعمال کر سکتا ہوں؟ اگر ایسا ہے تو، سٹڈ اپنا کام کر رہا ہے.

ایک بار کلک کرنے کے بعد، وہی عادت شکل کو تبدیل کیے بغیر صرف سائز میں پھیل جائے گی۔ شاپنگ ایپ میں موجود پروڈکٹ کارڈز دراصل ایک ہی خیال ہیں، صرف یہ کہ بچے قدرے بڑے ہیں۔

class ProductThumbnail extends StatelessWidget {
  final String imageUrl;
  const ProductThumbnail({super.key, required this.imageUrl});

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(6),
      child: Image.network(imageUrl, height: 120, fit: BoxFit.cover),
    );
  }
}

class ProductCard extends StatelessWidget {
  final String name;
  final double price;
  final String imageUrl;

  const ProductCard({
    super.key,
    required this.name,
    required this.price,
    required this.imageUrl,
  });

  @override
  Widget build(BuildContext context) {
    return SurfaceCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          ProductThumbnail(imageUrl: imageUrl),
          Text(name, style: const TextStyle(fontWeight: FontWeight.bold)),
          Text('$${price.toStringAsFixed(2)}'),
        ],
      ),
    );
  }
}

یہاں تصوراتی طور پر کچھ نیا نہیں ہوا۔ ProductThumbnail یہ اس کا اپنا چھوٹا بلاک ہے جو صرف تصاویر کو لوڈ کرنے اور تراشنے کا ذمہ دار ہے۔ لہذا اگر آپ بعد میں سوئچ کرتے ہیں۔ Image.network کیشڈ امیج پیکج میں بالکل ایک فائل کو تبدیل کیا گیا ہے اور اسے استعمال کرنے والے کچھ بھی نہیں دیکھیں گے۔

ProductCard اب ہم اپنے طور پر کچھ نہیں بناتے۔ پہلے سے موجود اینٹوں کو ترتیب دینا (SurfaceCard باکس اور ProductThumbnail تصویر کے معاملے میں)، یہ اسی طرح ہے جس طرح آپ مختلف خانوں سے ایک چھوٹے ماڈل میں دو ٹکڑے کرتے ہیں۔

تینوں ویجٹ کے لیے تمام درآمدات اب بھی سادہ ہیں۔ package:flutter/material.dart. یہاں آنے کے لیے کسی نئے پیکیج کی ضرورت نہیں تھی۔ کیونکہ اس سطح پر LEGO سوچ ایک لائبریری نہیں ہے، یہ فیصلہ کرتا ہے کہ باکس کے درمیان لائن کہاں کھینچنی ہے اور اس کے اندر کیا ہے۔

سٹڈ میسنری: کنکریٹ انحصار کے بجائے معاہدے

اکیلے کمپوزیشن سے آپ کو دوبارہ قابل استعمال UI مل سکتا ہے، لیکن ابھی تک قابل تبدیلی رویہ نہیں۔ اس کے لیے اینٹوں اور اس پر منحصر ہر چیز کے درمیان ایک واضح معاہدہ (عام طور پر ایک تجریدی کلاس یا فنکشن کی قسم) کی ضرورت ہوتی ہے۔

فرض کریں ProductCard مجھے کارٹ میں پروڈکٹس شامل کرکے ٹیب پر ردعمل ظاہر کرنے کی ضرورت ہے، لیکن میں نہیں چاہتا کہ کارڈ خود یہ جان لے کہ آیا اس کا مطلب REST API کو کال کرنا، مقامی اسٹوریج پر لکھنا، یا ڈیمو کے دوران کنسول پر پرنٹ کرنا ہے۔

abstract class CartWriter {
  Future add(String productId);
}

class ApiCartWriter implements CartWriter {
  final Dio client;
  ApiCartWriter(this.client);

  @override
  Future add(String productId) async {
    await client.post('/cart/items', data: {'productId': productId});
  }
}

class InMemoryCartWriter implements CartWriter {
  final List items = [];

  @override
  Future add(String productId) async {
    items.add(productId);
  }
}

اور ویجٹ صرف معاہدوں سے بات کرتے ہیں۔

class AddToCartButton extends StatelessWidget {
  final String productId;
  final CartWriter cartWriter;

  const AddToCartButton({
    super.key,
    required this.productId,
    required this.cartWriter,
  });

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => cartWriter.add(productId),
      child: const Text('Add to cart'),
    );
  }
}

abstract class CartWriter یہ ایک جڑنا ہے۔ بالکل ایک فنکشن کا اعلان کرتا ہے۔ add(String productId)اس پر عمل درآمد کے بارے میں کچھ نہیں کہا گیا ہے۔ یہ ایک معاہدہ ہے، پچھلے حصے سے مادّہ کاری کا اصول نہیں، لفظی طور پر کوڈ میں لکھا گیا ہے۔

ApiCartWriter یہ ایک اینٹ ہے جو اس کا استعمال کرتے ہوئے معاہدے کو پورا کرتی ہے: Dioمقبول HTTP کلائنٹ پیکجز import 'package:dio/dio.dart'; ایک حقیقی پروجیکٹ میں، یہ فائل کے اوپری حصے میں ہوگا۔ یہ نیٹ ورکنگ کی تمام تفصیلات کا مالک ہے، لہذا اس کلاس سے باہر کسی بھی چیز کو اختتامی نقطہ URL یا درخواست کی شکل جاننے کی ضرورت نہیں ہے۔ InMemoryCartWriter ایک دوسری اینٹ جو اسی معاہدے کو پورا کرتی ہے۔ یہ جانچ، پیش نظارہ، یا آف لائن ڈیمو کے لیے مفید ہے اور اس کا اپنا کوئی انحصار نہیں ہے۔ کوئی Dio یا نیٹ ورک نہیں۔

AddToCartButton خلل CartWriter خود کو انسٹیٹیوٹ کرنے کے بجائے کنسٹرکٹر کے ذریعے۔ اسے انحصار انجیکشن کہا جاتا ہے، اور یہ وہ طریقہ کار ہے جو معاہدوں کو واقعی کارآمد بناتا ہے کیونکہ ویجیٹ خود بنانے کے بجائے باہر سے اینٹوں کو اندر سے گزارتا ہے۔

یہ ایک انعام ہے جس کے لیے توقف کیا جا سکتا ہے۔ اب آپ ویجیٹ ٹیسٹ لکھ سکتے ہیں جو پاس ہوتا ہے۔ InMemoryCartWriter اور یہ دعویٰ کرتا ہے کہ: cartWriter.items اس میں صحیح پروڈکٹ ہے، کوئی فرضی فریم ورک یا نیٹ ورک اسٹبس کی ضرورت نہیں ہے۔

آرکیٹیکچر ڈایاگرام جو CartWriter خلاصہ کلاس پر مبنی AddToCartButton دکھا رہا ہے، جو ایک مشترکہ معاہدے کے طور پر کام کرتا ہے اور نیٹ ورک آپریشنز کے لیے ApiCartWriter اور ان میموری ٹیسٹنگ کے لیے InMemoryCartWriter سے جڑتا ہے۔

فولڈر کی سطح پر LEGO

اگر ہم یہ قبول کرتے ہیں کہ انفرادی کلاسوں کو معاہدوں کے ذریعے ایک ساتھ جوڑنا ضروری ہے، تو وہی منطق اس پر لاگو ہوتی ہے کہ فولڈرز کو کس طرح منظم کیا جاتا ہے۔

ایک عام ابتدائی غلطی قسم کے لحاظ سے ترتیب دینا ہے۔ screens فولڈر، widgets فولڈر اور services فولڈرز ساتھ ساتھ بیٹھتے ہیں۔ یہ صاف نظر آتا ہے، لیکن یہ اس کے بالکل برعکس ہے جو آپ لیگو کے بارے میں سوچتے ہیں۔ شاپنگ کارٹ کی فعالیت کو سمجھنے یا تبدیل کرنے کے لیے تین غیر متعلقہ فولڈرز کے درمیان چھلانگ لگانے کی ضرورت ہوتی ہے، اور شاپنگ کارٹ سروس فائل کو پروڈکٹ اسکرین فائل سے خاموشی سے کچھ نکالنے سے روکنے کے لیے کچھ بھی نہیں ہے۔ کوئی بھی چیز واقعی خود مختار نہیں ہے۔

اس کے بجائے، LEGO کے موافق ورژن کو خصوصیت کے ذریعے ترتیب دیا گیا ہے۔ ہر فنکشن اس کی اپنی اینٹ ہے، جس میں ہر وہ چیز ہوتی ہے جس کی اسے ضرورت ہوتی ہے اور صرف وہی ظاہر کرتا ہے جو دوسرے فنکشنز کو چھو سکتے ہیں۔

lib/
  features/
    product/
      product.dart          <- "barrel" file: the public stud
      src/
        widgets/
          product_card.dart
          product_thumbnail.dart
        services/
          cart_writer.dart
        models/
          product.dart
    cart/
      cart.dart
      src/
        widgets/
          cart_item.dart
        services/
          cart_repository.dart
  core/
    theme/
    routing/
    network/

یہاں کلیدی فائلیں ہیں: product.dartیہ ایک بیرل فائل ہے جو صرف دوسری خصوصیات کو برآمد کرتی ہے جو آپ استعمال کریں گے۔

// lib/features/product/product.dart
library product;

export 'src/widgets/product_card.dart';
export 'src/models/product.dart';
// note: cart_writer.dart is intentionally NOT exported.
// it's an internal implementation detail of this feature.

کہ library product; لائن اس فائل کو ایپ کے اندر موجود پروڈکٹ پیکیج کے اندراج کے نقطہ کے طور پر نام دیتی ہے۔ یہ ایک اصول ہے، بذات خود کوئی سخت حد نہیں۔ کہ export بیان منتخب فائلوں کو دوبارہ برآمد کرتا ہے، لہذا جو فائلیں یہاں درج نہیں ہیں ان میں شامل ہیں: cart_writer.dartخصوصیت نجی رہتی ہے۔ دیگر افعال استعمال کیے جاتے ہیں۔ import 'package:app/features/product/product.dart'; آپ اسے صرف نہیں دیکھ سکتے۔

یہ LEGO کے حقیقی آئیڈیا کی درست عکاسی کرتا ہے۔ src/ اینٹوں کے اندر، ڈھلے ہوئے پلاسٹک، بیرل کے ڈھیر جڑے ہوئے ہیں۔ یہ واحد سطح ہے جسے دوسری اینٹیں چھو سکتی ہیں۔

آپ درحقیقت ڈارٹ کا استعمال کرکے اس حد کو نافذ کرسکتے ہیں۔ analysis_options.yaml لنٹنگ پیکیج کی درآمد کے ساتھ، یا صرف کوڈ کے جائزے کے نقطہ نظر کے ذریعے: اندر کوئی فائلیں نہیں ہیں۔ features/cart/src/ اسے ابھی تک درآمد کرنا ہوگا۔ src/ فائل کا ماخذ features/product/. اگر کارٹ کو واقعی پروڈکٹ سے کسی چیز کی ضرورت ہو تو اسے بیرل فائل مل جاتی ہے۔ product.dartداخلہ کو براہ راست ترتیب نہ دیں۔

ایک آرکیٹیکچرل ڈایاگرام جس میں دکھایا گیا ہے کہ کس طرح ایک بیرل فائل کے ذریعے نجی انٹرنلز تک رسائی حاصل کی جاتی ہے جو اندرونی نفاذ کی تفصیلات کو چھپاتے ہوئے فنکشن کے عوامی API کو بے نقاب کرتی ہے۔

انٹر ماڈیول کنٹریکٹس: ریپوزٹریز اور سروس لوکیٹر

فولڈر کی حدود دوسرے فنکشنز کو اندر کی چیزوں کو حاصل کرنے سے روکتی ہیں، لیکن حقیقی ایپس کو ان حدود میں عمل درآمد کرنا ضروری ہے۔ مثال کے طور پر، خریداری کی ٹوکری کی خصوصیت کو پروڈکٹ فیچر کی مخصوص سروس کلاس پر انحصار کیے بغیر پروڈکٹ کی قیمتوں کو بازیافت کرنے کے قابل ہونے کی ضرورت ہے۔ یہ وہ جگہ ہے جہاں ریپوزٹری پیٹرن اور سروس لوکیٹر آتے ہیں۔

سب سے پہلے، معاہدہ دو افعال کے اندر کی بجائے مشترکہ، غیر جانبدار مقام پر ہے۔

// lib/core/contracts/product_lookup.dart
abstract class ProductLookup {
  Future priceOf(String productId);
}

مصنوعات کی خصوصیات حقیقی دنیا کا نفاذ فراہم کرتی ہیں۔

// lib/features/product/src/services/product_repository.dart
import 'package:app/core/contracts/product_lookup.dart';

class ProductRepository implements ProductLookup {
  final Map _cachedPrices;
  ProductRepository(this._cachedPrices);

  @override
  Future priceOf(String productId) async {
    return _cachedPrices[productId] ?? 0;
  }
}

شاپنگ کارٹ کی فعالیت صرف اس پر انحصار کرتی ہے: ProductLookupاصل اینٹوں کو سروس لوکیٹر کے ذریعے جوڑا جاتا ہے، جو کہ ایک رجسٹری ہے جس میں کنٹریکٹ کی قسم کے مطابق ترتیب دی جاتی ہے۔ get_it یہ اس کے لیے معیاری پیکج ہے۔

// lib/core/di/service_locator.dart
import 'package:get_it/get_it.dart';
import 'package:app/core/contracts/product_lookup.dart';
import 'package:app/features/product/src/services/product_repository.dart';

final getIt = GetIt.instance;

void setupServiceLocator() {
  getIt.registerLazySingleton(
    () => ProductRepository({'p1': 19.99, 'p2': 4.50}),
  );
}
// lib/features/cart/src/services/cart_calculator.dart
import 'package:app/core/contracts/product_lookup.dart';
import 'package:app/core/di/service_locator.dart';

class CartCalculator {
  final ProductLookup _productLookup;

  CartCalculator({ProductLookup? productLookup})
      : _productLookup = productLookup ?? getIt();

  Future total(List productIds) async {
    double sum = 0;
    for (final id in productIds) {
      sum += await _productLookup.priceOf(id);
    }
    return sum;
  }
}

لائن import 'package:get_it/get_it.dart'; سروس لوکیٹر پیکج درآمد کریں اور GetIt.instance پوری ایپ کے ذریعہ مشترکہ واحد عالمی رجسٹری (سنگلٹن) فراہم کرتا ہے۔

فون کال registerLazySingleton(...) جب کوئی درخواست کرتا ہے، تو وہ لوکیٹر سے کہتے ہیں: ProductLookupمجھے یہ ایک مثال ان کے حوالے کرنے کی ضرورت ہے۔ ProductRepository. اسے صرف اس وقت بنایا جانا چاہئے جب پہلی بار درخواست کی جائے۔

یہاں اہم چیز عام قسم کا پیرامیٹر ہے۔ اس کی وجہ یہ ہے کہ رجسٹری کی چابیاں معاہدہ سے نہیں بلکہ معاہدے سے طے کی جاتی ہیں۔ ProductRepository. یہ معاہدہ کے تحت نفاذ کا طریقہ کار ہے۔

setupServiceLocator() عام طور پر ایک بار بلایا جاتا ہے۔ main()پہلے runApp()یہ آپ کی ایپ کے لیے واحد اسمبلی پوائنٹ بن جاتا ہے۔ کنکریٹ اینٹوں کی تمام چیزوں کے بارے میں جاننے کے لیے واحد جگہ۔

CartCalculatorکنسٹرکٹر اختیارات کو قبول کرتا ہے۔ ProductLookupلوکیٹر کی طرف سے فراہم کردہ کوئی بھی چیز ڈیفالٹ پر سیٹ ہو جائے گی۔ یہ اختیاری پیرامیٹر چال آپ کی کلاس کو آسانی سے قابل آزمائش بنانا ہے۔ کیونکہ امتحان جعلی کے طور پر پاس ہوتا ہے۔ ProductLookup پیداوار کے ذریعے کام کرتے ہوئے getIt.

توجہ فرمائیں cart_calculator.dart کبھی بھی کچھ درآمد نہ کریں۔ features/product/src/. صرف مشترکہ معاہدے اور لوکیٹر درآمد کیے جاتے ہیں۔ آپ اصل بیک اینڈ کالز کے لیے ان میموری میپس کو بدل کر، شروع سے ہی اپنے پروڈکٹ کی فعالیت کو دوبارہ لکھ سکتے ہیں۔ cart_calculator.dart آپ کو ایک لائن میں ترمیم کرنے کی ضرورت نہیں ہے۔ ProductRepository ابھی تک عمل درآمد میں ہے۔ ProductLookup.

پیمانے کے لحاظ سے یہ لیگو آرکیٹیکچر کی سب سے اہم چال ہے۔ معاہدہ ایک اندرونی، غیر جانبدار زون میں ہے۔ core/contracts/کنکریٹ کی اینٹ اس فنکشن کے اندر ہے جو اس کا مالک ہے اور واحد وائرنگ پوائنٹ (سروس لوکیٹر) دونوں کو حاصل کرنے کی واحد جگہ ہے۔

LEGO سیٹ کی طرح مکمل طور پر فعال

کلین آرکیٹیکچر سے موازنہ کرنے سے پہلے آخری مرحلہ یہ ہے کہ پوری فعالیت کو ایک پلگ ایبل ماڈیول کے طور پر سمجھا جائے جسے ایپ شیل اسٹارٹ اپ پر جمع کرتا ہے۔ یہ اسی طرح ہوتا ہے جس طرح LEGO ہدایات کتابچہ آپ کو بتاتا ہے کہ کون سی ذیلی اسمبلی بیس پلیٹ کے ساتھ منسلک ہوتی ہے۔

// lib/core/feature_module.dart
import 'package:go_router/go_router.dart';

abstract class FeatureModule {
  List get routes;
  void registerDependencies();
}
// lib/features/cart/cart_module.dart
import 'package:go_router/go_router.dart';
import 'package:app/core/feature_module.dart';
import 'package:app/core/di/service_locator.dart';
import 'src/screens/cart_screen.dart';
import 'src/services/cart_calculator.dart';

class CartModule implements FeatureModule {
  @override
  void registerDependencies() {
    getIt.registerFactory(() => CartCalculator());
  }

  @override
  List get routes => [
        GoRoute(path: '/cart', builder: (context, state) => const CartScreen()),
      ];
}
// lib/app.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'features/cart/cart_module.dart';
import 'features/product/product_module.dart';
import 'core/feature_module.dart';

final List modules = [
  ProductModule(),
  CartModule(),
];

GoRouter buildRouter() {
  for (final module in modules) {
    module.registerDependencies();
  }
  return GoRouter(
    routes: modules.expand((m) => m.routes).toList(),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(routerConfig: buildRouter());
  }
}

FeatureModule یہ ایپ میں اعلیٰ سطح کا اسٹڈ ہے۔ کوئی بھی فنکشن جسے آپ شیل سے جوڑنا چاہتے ہیں اسے فراہم کرنا ضروری ہے: routesاس سے مراد وہ سکرین ہے جو بے نقاب ہے، registerDependencies()اس کا مطلب ہے کہ آپ کو سروس لوکیٹر سے جڑنے کی ضرورت ہے۔

CartModule اس معاہدے کو نافذ کریں اور اندرونی طور پر registerDependencies() یہ رجسٹر کرتا ہے CartCalculator فیکٹری کو۔ سنگلٹن کے برعکس، جب بھی درخواست کی جاتی ہے تو یہ ایک نئی مثال ہے۔ ProductRepository پچھلے حصے سے۔ رجسٹریشن کا انداز ایک فیصلہ ہے جو ہر فنکشن اپنے طور پر کرتا ہے۔

درآمد package:go_router/go_router.dart ملتا ہے go_router پیکج یہ گردش RouteBase ٹاسک نیویگیشن اسٹیک میں ایک آبجیکٹ شامل کریں اور GoRoute(path: ..., builder: ...) کسی راستے کا نقشہ بناتا ہے، جیسے کہ یو آر ایل، اسکرین پر۔ app.dart یہ پوری ایپلیکیشن کی اصل کنفیگریشن جڑ ہے۔ کہ modules فہرست ایک ہدایاتی کتابچہ ہے اور پوری ایپ میں واحد فائل ہے جو جانتی ہے کہ تمام خصوصیات موجود ہیں۔ ہر ماڈیول کے ذریعے اعادہ کریں، اس کے اپنے انحصار کو رجسٹر کریں اور تمام راستوں کو ایک میں چپٹا کریں۔ GoRouter.

اگر آپ اپنی ایپ میں بالکل نئی فعالیت شامل کرنا چاہتے ہیں تو ایک نئی خصوصیت لکھیں۔ FeatureModule نافذ کریں اور modules یہ ایک فہرست ہے اور موجودہ فیچر فائلوں کو چھوا نہیں جائے گا۔ یہ لیگو کا وعدہ مکمل طور پر پورا ہوا ہے۔ جب آپ اپنے سیٹ میں نئی ​​اینٹیں شامل کرتے ہیں، تو باکس میں پہلے سے موجود اینٹوں کو دوبارہ ڈھالنے کی ضرورت نہیں ہے۔

ایک آرکیٹیکچر ڈایاگرام جو ایک ایپلیکیشن انٹری پوائنٹ کے طور پر دکھایا گیا ہے جو انحصار کو رجسٹر کرتا ہے اور راؤٹر کو جمع کرتا ہے، جبکہ اسٹینڈ اسٹون فنکشن ماڈیولز اور مستقبل کے فنکشن ماڈیولز سے روٹس بھی اکٹھا کرتا ہے جنہیں آزادانہ طور پر منسلک کیا جا سکتا ہے۔

یہ لیگو آرکیٹیکچر ہے۔ وجیٹس تعمیر کیے جاتے ہیں، کلاسز کا انحصار معاہدوں پر ہوتا ہے، فولڈرز حدود کو نافذ کرتے ہیں، ماڈیول لائنوں کے درمیان معاہدے لوکیٹر کے ذریعے نافذ کیے جاتے ہیں، اور مجموعی طور پر فعالیت ایپ شیل میں آتی ہے۔ FeatureModule معاہدہ اب ہم صاف فن تعمیر کو دیکھ سکتے ہیں اور برابری کی بنیاد پر دونوں کا موازنہ کر سکتے ہیں۔

کلین آرکیٹیکچر کریش کورس

کلین آرکیٹیکچر، جسے رابرٹ سی. مارٹن نے مقبول بنایا، ایک مخصوص تہہ بندی کی اسکیم ہے جسے ایک ہی اصول کے گرد بنایا گیا ہے جسے انحصار کے اصول کے نام سے جانا جاتا ہے۔ ماخذ کوڈ پر انحصار صرف اندرونی، یعنی اعلیٰ سطح کی پالیسیوں کی طرف اشارہ کر سکتا ہے۔ اندرونی تہہ میں کوئی بھی چیز بیرونی تہہ کے بارے میں کچھ نہیں جان سکتی۔

یہ آرکیٹیکچرل ڈایاگرام کے ساتھ بنیادی انحصار کے اصولوں کی وضاحت کرتا ہے جس میں پریزنٹیشن اور ڈیٹا لیئرز پر انحصار کرتے ہوئے اور عمل درآمد کے لیے مرکزی ڈومین پرت کی طرف اشارہ کرتے ہوئے دکھایا گیا ہے۔

آئیے ڈومین پرت اور اس کے اداروں کے ساتھ شروع کریں، مشترکہ فریم ورک کے بغیر اشیاء، اور تینوں پرتوں کے ذریعے ایک فنکشن (آئی ڈی کے ذریعہ پروڈکٹ حاصل کریں) بنائیں۔

// lib/features/product/domain/entities/product.dart
class Product {
  final String id;
  final String name;
  final double price;

  const Product({required this.id, required this.name, required this.price});
}

ڈومین پرت میں درج ذیل اسٹوریج پورٹس ہیں، جو انٹرفیس ہیں جن کی ڈومین وضاحت کرتا ہے لیکن عمل درآمد نہیں کرتا ہے۔

// lib/features/product/domain/repositories/product_repository.dart
import '../entities/product.dart';

abstract class ProductRepository {
  Future getById(String id);
}

ڈومین پرت پر استعمال کا معاملہ پھر ایک نامی کاروباری آپریشن ہے۔

// lib/features/product/domain/usecases/get_product.dart
import '../entities/product.dart';
import '../repositories/product_repository.dart';

class GetProduct {
  final ProductRepository repository;
  GetProduct(this.repository);

  Future call(String id) => repository.getById(id);
}

ڈیٹا پرت اب اسٹوریج کے نفاذ کے ساتھ شروع ہوتی ہے جو ڈومین کی پورٹس کو مطمئن کرتی ہے۔

// lib/features/product/data/repositories/product_repository_impl.dart
import 'package:app/features/product/domain/entities/product.dart';
import 'package:app/features/product/domain/repositories/product_repository.dart';
import '../datasources/product_remote_data_source.dart';

class ProductRepositoryImpl implements ProductRepository {
  final ProductRemoteDataSource remoteDataSource;
  ProductRepositoryImpl(this.remoteDataSource);

  @override
  Future getById(String id) async {
    final dto = await remoteDataSource.fetchProduct(id);
    return Product(id: dto.id, name: dto.name, price: dto.price);
  }
}

اور یہ ریموٹ ڈیٹا سورس ہے جو اصل HTTP کال اور خام JSON فارم کا مالک ہے۔

// lib/features/product/data/datasources/product_remote_data_source.dart
import 'package:dio/dio.dart';

class ProductDto {
  final String id;
  final String name;
  final double price;
  ProductDto({required this.id, required this.name, required this.price});

  factory ProductDto.fromJson(Map json) => ProductDto(
        id: json['id'],
        name: json['name'],
        price: (json['price'] as num).toDouble(),
      );
}

class ProductRemoteDataSource {
  final Dio client;
  ProductRemoteDataSource(this.client);

  Future fetchProduct(String id) async {
    final response = await client.get('/products/$id');
    return ProductDto.fromJson(response.data);
  }
}

آخر میں، پریزنٹیشن پرت کیوبٹ ہے، جو استعمال کے کیسز کو کال کرتی ہے۔

// lib/features/product/presentation/cubit/product_cubit.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app/features/product/domain/entities/product.dart';
import 'package:app/features/product/domain/usecases/get_product.dart';

sealed class ProductState {}
class ProductLoading extends ProductState {}
class ProductLoaded extends ProductState {
  final Product product;
  ProductLoaded(this.product);
}
class ProductError extends ProductState {
  final String message;
  ProductError(this.message);
}

class ProductCubit extends Cubit {
  final GetProduct getProduct;
  ProductCubit(this.getProduct) : super(ProductLoading());

  Future load(String id) async {
    emit(ProductLoading());
    try {
      final product = await getProduct(id);
      emit(ProductLoaded(product));
    } catch (e) {
      emit(ProductError(e.toString()));
    }
  }
}

آئیے ہر پرت کو ترتیب سے دیکھیں۔

سب سے پہلے entities/product.dart کوئی درآمدات نہیں ہیں۔ یہ جان بوجھ کر ہے کیونکہ یہ ڈومین پرت کا واحد سب سے اہم اصول ہے۔ اس کا مطلب ہے کہ آپ فلٹر، ڈیو، یا کوئی اور فریم ورک درآمد نہیں کر سکتے۔ چونکہ یہ خالص ڈارٹ ہے، اسے بغیر کسی ترمیم کے کمانڈ لائن ٹولز یا بیک اینڈ میں دوبارہ استعمال کیا جا سکتا ہے۔

repositories/product_repository.dart پورٹ ایک تجریدی کلاس ہے۔ ڈومین پرت اس کی وضاحت کرتی ہے جس کی ضرورت ہے۔ getByIdلیکن آپ کبھی نہیں جانتے کہ اسے کیسے حاصل کرنا ہے۔ یہ وہی روح ہے۔ CartWriter اور ProductLookup پچھلے حصے سے۔ سب کے بعد، کلین آرکیٹیکچر نے انحصار الٹنے کی ایجاد نہیں کی، یہ صرف ہر سیون پر اسے منظم طریقے سے لاگو کرتا ہے۔

usecases/get_product.dart ایک کاروباری کام مکمل کریں۔ GetProduct نافذ کرنا call(String id)آپ افعال جیسے مثالوں کو کال کرسکتے ہیں۔ getProduct('p1'). کنسٹرکٹر ہے۔ ProductRepository (دوبارہ ایک خلاصہ بندرگاہ)، کبھی بھی ٹھوس بندرگاہ نہیں۔ ProductRepositoryImpl.

data/datasources/product_remote_data_source.dart اپنے import 'package:dio/dio.dart'; وائر فارمیٹ کے بارے میں تمام معلومات کے ساتھ ProductDto.fromJson. یہ مکمل فعالیت والی واحد فائل ہے جو آپ کو بتاتی ہے کہ سرور پر خام JSON کیسا لگتا ہے۔

data/repositories/product_repository_impl.dart ڈومینز کی بندرگاہوں کو نافذ کرکے اور شکلوں کے درمیان تبدیل کرکے، ProductDto واپسی (ڈیٹا ریجن کی شکل) اور Product (ڈومین پرت کی شکل)۔ یہ تبدیلی کا مرحلہ ڈومین پرت کو JSON کو مکمل طور پر نظر انداز کرنے کی اجازت دیتا ہے۔

presentation/cubit/product_cubit.dart درآمد شدہ سامان package:flutter_bloc/flutter_bloc.dart کے لیے Cubitاور ڈومین میں GetProduct اور Productلیکن سے کچھ نہیں۔ data/. کہ sealed class ProductState تین ذیلی طبقات (ProductLoading، ProductLoadedاور ProductError) واضح طور پر تمام ممکنہ UI ریاستوں کا ماڈل بناتا ہے، جس سے ویجیٹ پرت کو بغیر اندازہ لگائے منتقلی کی اجازت ملتی ہے۔

اس کو جو آپس میں جوڑتا ہے وہ کمپوزیشن روٹ کا کلین آرکیٹیکچر ورژن ہے جو پہلے متعارف کرایا گیا تھا۔

// lib/features/product/product_injection.dart
import 'package:dio/dio.dart';
import 'package:get_it/get_it.dart';
import 'domain/repositories/product_repository.dart';
import 'domain/usecases/get_product.dart';
import 'data/datasources/product_remote_data_source.dart';
import 'data/repositories/product_repository_impl.dart';

void registerProductFeature(GetIt getIt) {
  getIt.registerLazySingleton(() => Dio());
  getIt.registerLazySingleton(() => ProductRemoteDataSource(getIt()));
  getIt.registerLazySingleton(
    () => ProductRepositoryImpl(getIt()),
  );
  getIt.registerFactory(() => GetProduct(getIt()));
}

یہ فائل مکمل فعالیت کے لیے واحد جگہ ہے جہاں آپ تمام پرتوں کو ایک ساتھ دیکھ سکتے ہیں: ڈومین، ڈیٹا، کنکریٹ، وغیرہ۔ Dio گاہک یہ بالکل وہی ذمہ داری ہے۔ service_locator.dart اور CartModule یہ گزشتہ LEGO کیسز میں منعقد کیا گیا ہے.

کلین آرکیٹیکچر بمقابلہ لیگو آرکیٹیکچر

اس مقام پر ان دونوں فن تعمیرات کے درمیان مماثلت بہت واضح ہونی چاہیے۔ دونوں کنکریٹ کے ٹکڑوں کو جمع کرنے کے لیے کنٹریکٹ کے بجائے کنٹریکٹ پر مبنی انحصار کے الٹ پر بنائے گئے ہیں، اور دونوں سنگل وائرنگ پوائنٹس کا استعمال کرتے ہیں۔

فرق یہ ہے کہ ہر ایک جوابات کے لیے موزوں ہے۔

LEGO فن تعمیر کو تعمیری صلاحیت اور حدود کے بارے میں سوچنے یا فلسفہ کے طور پر بہترین طریقے سے بیان کیا گیا ہے۔ آپ عمارت کے بلاکس جیسے ویجٹ، خدمات، یا پورے فنکشنل ماڈیولز کو منتخب کر سکتے ہیں۔

جہاں بھی آپ انہیں رکھنے کا فیصلہ کرتے ہیں وہاں حدود موجود ہوتی ہیں: فولڈرز، بیرل فائلز، ماڈیولر کنٹریکٹس وغیرہ میں۔ آپ اینٹوں کا سائز یا طول و عرض بھی منتخب کر سکتے ہیں۔ بنیادی مقصد تبادلہ ہے، لہذا کسی بھی ٹکڑے کو اس کے پڑوسیوں کو نقصان پہنچائے بغیر تبدیل کیا جا سکتا ہے۔

LEGO فن تعمیر میں شروع کرنے کے لیے سیکھنے کا کم وکر ہے کیونکہ اسے پہلے درجے کے لیے فلٹر کے علاوہ کسی اور چیز کی ضرورت نہیں ہوتی ہے اور جب آپ مزید بعد کی سطحوں کو اپناتے ہیں تو بتدریج پھیلتا ہے۔ زیادہ سے زیادہ بوائلر پلیٹ شامل کریں جتنا آپ شامل کرنے کا انتخاب کرتے ہیں۔

ان ایپس کے لیے بہترین موزوں ہے جن کو خصوصیت کی لچکدار حدود، متوازی آپریشنز، اور بتدریج اپنانے کی ضرورت ہوتی ہے۔ بنیادی خطرہ اسے صرف نام سے LEGO کہنا ہے۔ اینٹیں خاموشی سے ایک دوسرے کے اندر پہنچ رہی ہیں، حالانکہ فولڈر کا ڈھانچہ دوسری صورت میں تجویز کرتا ہے۔

اس کے برعکس، کلین آرکیٹیکچر ایک مخصوص، نامی لیئرنگ اسکیم ہے جس کی ایک مقررہ شکل ہے (پریزنٹیشن، ڈومین، ڈیٹا، وغیرہ) اور جس کے انحصار کے اصول ہمیشہ اندر کی طرف اشارہ کرتے ہیں۔

آرگنائزنگ یونٹس خاص طور پر ادارے، استعمال کے کیسز اور ریپوزٹریز ہیں۔ بنیادی اہداف فریم ورک، UI، اور ڈیٹا بیس سے آزمائش اور آزادی ہیں، اور وہ کافی بکھرے ہوئے ہوتے ہیں، بنیادی طور پر دہرائے جانے والے فنکشن بہ فنکشن تجویز کردہ ڈھانچے کے ساتھ۔

سیکھنے کا منحنی خطوط زیادہ تیز ہے کیونکہ اسے پہلے دن سے فی فنکشن ایک سے زیادہ فائلوں کی ضرورت ہوتی ہے اور اس میں نمایاں طور پر زیادہ بوائلر پلیٹ فی فنکشن ہوتا ہے، بشمول اداروں، استعمال کے کیسز، دو اسٹوریج لیئرز، DTOs اور qubits یا اسی طرح کے فنکشنز۔

پیچیدہ کاروباری اصولوں والی ایپس کے لیے بہترین موزوں ہے جنہیں UI یا فریم ورک کی تبدیلیوں سے آزاد ہونے کی ضرورت ہے۔ بنیادی خطرہ بوائلر پلیٹ کی خاطر بوائلر پلیٹ ہے۔ دوسرے الفاظ میں، آپ فعالیت کے لیے تین پرتیں بنا رہے ہیں جن کی حفاظت کے لیے کوئی حقیقی کاروباری منطق نہیں ہے۔

دونوں کے درمیان تعلق کے بارے میں سوچنے کا سب سے مفید طریقہ یہ ہے: کلین آرکیٹیکچر ایک ہی فنکشن کے ساتھ LEGO اینٹوں کو بنانے کے بہت اچھی طرح سے مخصوص طریقوں میں سے ایک ہے۔ ہستیاں، استعمال کے کیسز، اور ریپوزٹری خود اسٹڈز، انٹرفیس کے ساتھ اینٹ ہیں، اور انحصار انجیکشن کے ذریعے آپس میں جڑے ہوئے ہیں۔ یہ LEGO آئیڈیا ہے، جس میں فکسڈ، رائے والی شکلیں لگائی گئی ہیں۔

آپ لیگو کا انتخاب نہیں کر رہے ہیں۔ یا صاف فن تعمیر۔ منتخب کریں کہ کلین آرکیٹیکچر سے آپ اپنی LEGO اینٹوں میں کتنی مخصوص شکل لگانا چاہتے ہیں۔

دونوں کو ایک ماڈیولر Monorepo میں ضم کریں۔

اب تک کی ہر چیز نے ایک فلٹر پروجیکٹ کے اندر ایک فولڈر کا ڈھانچہ استعمال کیا ہے۔ بیرل کے ڈھیروں نے خصوصیات کو ایک دوسرے کے اندر پہنچنے سے روک دیا، لیکن ان کی حدود اب بھی صرف کنونشن تھیں۔ جسمانی طور پر فائلوں کو اندر سے روکنے والی کوئی چیز نہیں ہے۔ features/cart/ اندر سے فائلیں درآمد کریں۔ features/product/src/نظم و ضبط اور کوڈ کا جائزہ لینے کے علاوہ۔

پیداواری پیمانے پر، کچھ ٹیمیں ہر خصوصیت کو اس کے اپنے اصل ڈارٹ پیکج میں تبدیل کر کے اس فرق کو مکمل طور پر ختم کرتی ہیں، اس لیے حدود نظم و ضبط کے بجائے خود پیکیجنگ سسٹم کے ذریعے نافذ کی جاتی ہیں۔

اسے اکثر مونوریپو کہا جاتا ہے، اور فلٹر میں اس کا انتظام کرنے کے لیے سب سے زیادہ استعمال ہونے والا ٹول میلوس ہے۔ اس حصے کا بقیہ حصہ آپ کے سیٹ اپ کو شروع سے بناتا ہے، ایک وقت میں ایک چھوٹا سا قدم، لہذا حتمی فولڈر کے درخت کے بارے میں کچھ بھی ایسا محسوس نہیں ہوتا ہے جیسے یہ جادوئی طور پر ظاہر ہوا ہو۔

ایک خالی فولڈر سے شروع کریں۔

Flutter کمانڈ چلانے سے پہلے، آپ کے کمپیوٹر میں صرف ایک فولڈر ہوتا ہے جس میں Flutter سے متعلق کچھ بھی نہیں ہوتا ہے۔

mkdir my_lego_project
cd my_lego_project

اس وقت my_lego_project یہ فلٹر پروجیکٹ نہیں ہے۔ یہ وہاں نہیں ہے pubspec.yamlنہیں lib فولڈر، اور نہیں android فولڈر یہ صرف ایک باقاعدہ ڈائرکٹری ہے، جو فولڈرز کی طرح ہے جو آپ اپنے دستاویزات کو رکھنے کے لیے بناتے ہیں۔ اس کے بعد آنے والی ہر چیز جان بوجھ کر اس کے اندر بنائی گئی ہے، ایک وقت میں ایک ٹکڑا۔

اپنا پروجیکٹ ڈیلیور کریں جہاں مقامی کوڈ موجود ہو۔

اپنے فون کو چلانے کے لیے آپ کو ایک حقیقی اینڈرائیڈ پروجیکٹ اور ایک حقیقی iOS پروجیکٹ کی ضرورت ہوگی۔ تو اندر بنانے کے لئے پہلی چیز ہے my_lego_project یہ ایک باقاعدہ فلٹر ایپ ہے جس میں وہی کمانڈز ہیں جو آپ ہمیشہ استعمال کرتے آئے ہیں۔

mkdir apps
cd apps
flutter create app_main

flutter create app_main بالکل ویسا ہی برتاؤ کریں جیسا کہ آپ ہمیشہ کرتے ہیں۔ تخلیق کرتا ہے android/، ios/، lib/main.dartاور pubspec.yamlتمام اندر apps/app_main/. اس مرحلے پر ابھی تک LEGO سے متعلق کچھ نہیں ہے۔ اب تک صرف یہ فیصلہ کیا گیا ہے کہ یہ عام ایپ ڈسک پر کہاں رہتی ہے۔ apps یہ پروجیکٹ روٹ میں ہے، فولڈر میں نہیں۔

my_lego_project/
  apps/
    app_main/
      android/
      ios/
      lib/
        main.dart
      pubspec.yaml

یہ app_main فولڈر پورے پروجیکٹ میں واحد جگہ ہے جس میں شامل ہیں: android/ یا ios/. یہاں سے تیار کردہ دیگر تمام پیکجوں میں جان بوجھ کر وہ پیکیج شامل نہیں ہوگا۔

اپنی پہلی اینٹ بنانا

اب پروجیکٹ روٹ پر واپس جائیں اور دوسرا فولڈر بنائیں۔ packagesپاس بیٹھو apps.

cd ../..
mkdir packages
cd packages

اندر packagesاپنا پہلا فنکشن بنائیں۔ لیکن اس بار ہم ایک ہی فنکشن میں مختلف جھنڈے پاس کرتے ہیں۔ flutter create حکم

flutter create --template=package feature_login

پہلے سے صرف ایک فرق ہے۔ --template=package. اس کے بغیر، flutter create فرض کریں کہ آپ کو ایک قابل عمل ایپ کی ضرورت ہے، ایک ڈیفالٹ فولڈر بنائیں۔ یہ Flutter کو ایک عام لائبریری بنانے کی اجازت دیتا ہے، یعنی lib/ فولڈر، test/ فولڈر اور pubspec.yaml) جان بوجھ کر چھوڑ دیا گیا۔ android/، ios/اور web/. اس کی وجہ یہ ہے کہ اس طرح کے پیکیج خود نہیں چلتے۔ صرف ان ایپس میں درآمد کریں جن کے پاس وہ فولڈر ہے۔

my_lego_project/
  apps/
    app_main/            (has native folders)
  packages/
    feature_login/
      lib/
      test/
      pubspec.yaml

اس وقت، feature_login اور app_main ہم ایک دوسرے کے بارے میں کچھ نہیں جانتے۔ یہ دو غیر متعلقہ فولڈرز ہیں جو ڈسک پر ایک دوسرے کے قریب واقع ہیں۔

راستے پر انحصار کا استعمال کرتے ہوئے اینٹ کو اپنی ایپ سے جوڑیں۔

سیٹ app_main سے کوڈ استعمال کریں۔ feature_loginاسے انحصار کے طور پر شامل کریں۔ آپ یہ اسی طرح کر سکتے ہیں جیسے pub.dev میں پیکیج شامل کرنا، سوائے اس کے کہ یہ نام اور ورژن کی بجائے مقامی فولڈر کی طرف اشارہ کرتا ہے۔

# apps/app_main/pubspec.yaml
name: app_main
description: The actual iOS and Android wrapper application.

dependencies:
  flutter:
    sdk: flutter
  feature_login:
    path: ../../packages/feature_login

لائن path: ../../packages/feature_login کا رشتہ دار راستہ ہے۔ app_mainاپنے pubspec.yaml دو فولڈرز کا بیک اپ لیں۔ feature_login. یہ نہ تو میلوس کی کوئی خصوصیت ہے اور نہ ہی LEGO فن تعمیر کی ایجاد ہے۔ یہ ڈارٹ پیکیج مینیجر کی ایک عام خصوصیت ہے۔ path: مقامی پیکیج کی طرف اشارہ کرنے کے لیے استعمال ہونے والا انحصار۔

بچانے اور چلانے کے بعد flutter pub get اندر apps/app_main کافی lib/main.dart کو app_main لکھنا import 'package:feature_login/feature_login.dart'; جو بھی پیکج سامنے آئے اسے استعمال کریں۔

اس وقت پورا سیٹ اپ پہلے ہی کام کر رہا ہے، یہ بات قابل غور ہے کہ بالکل دو پیکجز ہیں اور میلوس کا ابھی تک کوئی ذکر نہیں ہے۔ میلوس کو ابھی تک متعارف نہیں کرایا گیا ہے کیونکہ یہ پیکجوں کے درمیان حدود نہیں بناتا ہے۔ باؤنڈری پہلے سے موجود ہے اور اس کے ذریعے نافذ کیا جاتا ہے: pubspec.yaml اور path: انحصار میلوس جو اضافہ کرتا ہے وہ یہ ہے کہ اگر متعدد پیکجوں میں دہرایا جائے تو یہ نمونہ آسان ہے۔ یہ اگلا مسئلہ حل کرنا ہے۔

melos.yaml اصل میں کہاں سے آتا ہے؟

melos.yaml یہ Flutter کمانڈز کے ذریعہ تیار نہیں ہوتا ہے، اور نہ ہی کوئی ٹول اسے خود بخود تیار کرتا ہے۔ پیکیج کو انسٹال کریں اور اس فائل کو براہ راست اپنے پروجیکٹ کی روٹ میں سادہ ٹیکسٹ فائل کے طور پر لکھیں۔

سب سے پہلے، میلوس کو اپنے کمپیوٹر پر ایک بار گلوبل ڈارٹ ٹول کے طور پر انسٹال کریں۔

dart pub global activate melos

پھر جڑ میں my_lego_projectکے پاس apps اور packages فولڈر میں ایک نئی فائل بنائیں، اسے نام دیں۔ melos.yaml پھر ٹائپ کریں:

name: my_lego_project

packages:
  - apps/**
  - packages/**
my_lego_project/
  melos.yaml
  apps/
    app_main/
  packages/
    feature_login/

کہ packages: یہاں کی فہرست گلوب پیٹرن کا استعمال کرتی ہے۔ apps/** اور packages/** میلوس کو دونوں فولڈرز کو دیکھنے اور تمام ذیلی فولڈرز پر کارروائی کرنے کی ہدایت کرتا ہے: pubspec.yaml ایک monorepo کے ایک رکن کے طور پر. یہاں کچھ بھی پوشیدہ یا خودکار نہیں ہے۔ آپ واضح طور پر میلوس کو بتا رہے ہیں کہ کہاں تلاش کرنا ہے۔

کیا melos bootstrap اصل میں

دو پیکجوں کا استعمال کرتے ہوئے چل رہا ہے flutter pub get ایک بار میں app_main اور ایک بار اندر feature_login یہ کوئی بوجھ نہیں ہے۔ میلوس کی قدر اس وقت ظاہر ہو جاتی ہے جب آپ کے پاس 10 سے 20 پیکجز ہوتے ہیں، جن میں سے ہر ایک کو انحصار کے حل کی ضرورت ہوتی ہے، اور جن میں سے ہر ایک مقامی راستوں کے ذریعے کئی دوسرے پیکجوں پر منحصر ہوتا ہے۔ ہر فولڈر کو براہ راست دیکھنے کے بجائے، پروجیکٹ روٹ سے ایک کمانڈ چلائیں۔

melos bootstrap

یہ واحد حکم ہے: melos.yamlنیچے تمام پیکیجز تلاش کریں۔ apps/** اور packages/**اور پھر درج ذیل فنکشن کو چلائیں: flutter pub get اپنے تمام مقامی مسائل کو ایک ساتھ حل کریں۔ path: راستے میں آپ پر منحصر ہے۔ یہ پہلے سے موجود میکانزم کے اوپر ایک آرکیسٹریشن ٹول ہے (عام میکانزم)۔ pubspec.yaml اور path: اوپر دکھائے گئے انحصار کو استعمال کرنے کے بجائے) یہ اپنا نیا طریقہ کار استعمال کرتا ہے۔

ماڈیولرٹی خود الگ الگ چیزوں سے آتی ہے۔ pubspec.yaml فائل اور واضح راستے پر انحصار۔ میلوس بہت سے کمانڈز کو تیزی سے اور بار بار چلانے کے لیے موجود ہے، اور بعد میں CI پائپ لائن میں صرف ان پیکجوں پر ٹیسٹ چلانے کے لیے جو حقیقت میں بدل گئے ہیں۔

اصل میں app_main کے lib فولڈر میں کیا ہے۔

اس مقام پر ایک فطری سوال یہ ہے کہ کیا ہر فیچر دراصل اس کا اپنا پیکیج ہوگا۔ بہر حال، عام فلٹر ڈویلپمنٹ میں، ایک پیکج کا مطلب عام طور پر دوبارہ قابل استعمال چیز ہوتا ہے، جیسے ڈیٹ چننے والا، پوری لاگ ان اسکرین نہیں۔

اس پیٹرن میں، پوری فعالیت، جیسے لاگ ان، اس کا اپنا پیکیج بن جاتا ہے، بشمول اسکرینز، اسٹیٹ مینجمنٹ، اور بزنس منطق۔ اس کی وجہ وہی کنٹینمنٹ مقصد ہے جو اس ہینڈ بک کی ہر سطح کو چلاتا ہے۔

اگر feature_login یہ اس کا اپنا پیکیج ہے اور یہ اس کے اندر کام کرنے والے ڈویلپرز ہیں۔ feature_home آپ غلطی سے اندر سے کوئی چیز درآمد نہیں کر سکتے feature_loginاس کی وجہ یہ ہے کہ اسے کبھی بھی انحصار قرار نہیں دیا گیا تھا۔ feature_homeاپنے pubspec.yaml. جائزہ لینے والے کے اسے دیکھنے کے بجائے، مرتب کرنے والا اسے براہ راست درآمد کرنے سے انکار کرتا ہے۔

اس سے دوسرا سوال پیدا ہوتا ہے۔ اگر اسکرینز، اسٹیٹ مینجمنٹ، اور منطق سب ایک فنکشنل پیکیج کے اندر ہیں، تو اس کے اندر کیا بچا ہے؟ app_main/lib? جواب ہے app_main/lib یہ بالکل تین ذمہ داریوں پر ابلتا ہے:

  1. یہ رکھتا ہے main.dartایپ کو بوٹ کریں اور اسے طلب کریں۔ runApp().

  2. انحصار انجیکشن کی ترتیبات رکھتا ہے۔ یہ پچھلے حصے سے کنفیگریشن روٹ کا حوالہ دیتا ہے، جہاں ایک ٹھوس نفاذ، جیسا کہ ایک حقیقی نیٹ ورک کلائنٹ، بنایا جاتا ہے اور مطلوبہ فیچر پیکجز کو منتقل کیا جاتا ہے۔

  3. اور اس میں ماسٹر راؤٹر ہے کیونکہ اس میں درج ذیل فیچر پیکج ہے۔ feature_login آپ جان بوجھ کر یہ نہیں جانتے feature_home یہ موجود ہے۔ تو بس app_mainدونوں پر منحصر ہے اور ایک سے دوسرے تک جانے کی پوزیشن میں ہے۔

یہاں نیویگیشن گلو پر ایک ٹھوس نظر ہے، جو فیچر پیکج کے اندر سے شروع ہوتی ہے۔

// packages/feature_login/lib/login_screen.dart
abstract class LoginNavigationContract {
  void onLoginSuccess();
}

class LoginScreen extends StatelessWidget {
  final LoginNavigationContract navigator;

  const LoginScreen({super.key, required this.navigator});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => navigator.onLoginSuccess(),
      child: const Text('Submit'),
    );
  }
}

feature_login وضاحت کریں LoginNavigationContractایک طریقہ کے ساتھ ایک تجریدی کلاس، onLoginSuccess(). LoginScreen دوسری فعالیت کو براہ راست درآمد کرنے کے بجائے کنسٹرکٹرز کے ذریعے عمل درآمد کی اجازت دیتا ہے۔

یہ وہی معاہدہ پیٹرن ہے جو اس ہینڈ بک میں استعمال ہوتا ہے، لیکن کلاس کی حدود کے بجائے پیکیج کی حدود پر لاگو ہوتا ہے۔ feature_login یہ بتاتا ہے کہ "اگلا" اصل میں کہاں ہے اس کا ذکر کیے بغیر آگے کیا ہونا چاہئے۔

app_main یہ واحد پیکیج ہے جس کے بارے میں میں دونوں کے لیے جانتا ہوں۔ feature_login اور feature_home چونکہ یہ موجود ہے، یہی سوال کا جواب ہے۔

// apps/app_main/lib/app_navigator.dart
import 'package:feature_login/feature_login.dart';
import 'package:feature_home/feature_home.dart';
import 'package:flutter/material.dart';

class AppNavigator implements LoginNavigationContract {
  final BuildContext context;
  AppNavigator(this.context);

  @override
  void onLoginSuccess() {
    Navigator.push(context, MaterialPageRoute(builder: (_) => const HomeScreen()));
  }
}

AppNavigator نافذ کرنا LoginNavigationContract یہ واحد جگہ ہے جو دونوں کو درآمد کرتی ہے۔ feature_login اور feature_home فوری طور پر جب onLoginSuccess() آگ لگائیں، دھکیل دیں۔ HomeScreenاندر وجیٹس feature_home. چلتی ایپ سے جڑنا دوبارہ ہوتا ہے۔ main.dart.

// apps/app_main/lib/main.dart
import 'package:flutter/material.dart';
import 'package:feature_login/feature_login.dart';
import 'app_navigator.dart';

void main() => runApp(const App());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: LoginScreen(navigator: AppNavigator(context)),
    );
  }
}

یہ پوری تصویر ہے۔ feature_login آپ اسکرین، توثیق، اور کامیاب لاگ ان کے بعد کیا ہونا چاہیے اس بارے میں سوالات کے مالک ہیں، اور اس کا اظہار صرف ایک معاہدے کے طور پر کیا جاتا ہے۔

app_mainاور صرف app_mainمیرے پاس مخصوص جوابات ہیں۔ android/، ios/، main.dartانحصار انجکشن اور روٹنگ. دیگر تمام پیکجز packages/ یہ اسی شکل کی پیروی کرتا ہے۔ feature_login: سے lib/ فولڈر، test/ فولڈر، pubspec.yaml واضح طور پر path: انحصار ہیں اور کوئی ڈیفالٹ فولڈر نہیں ہے۔ کیونکہ یہ پورے پروجیکٹ میں بالکل ایک جگہ موجود ہے۔

my_lego_project/
  melos.yaml
  apps/
    app_main/
      android/            <- only here
      ios/                <- only here
      lib/
        main.dart          owns: boot, DI, routing
        app_navigator.dart
      pubspec.yaml         depends on every feature package
  packages/
    feature_login/
      lib/                owns: login screens, logic
      pubspec.yaml         depends on nothing feature-specific
    feature_home/
      lib/                owns: home screens, logic
      pubspec.yaml

جب تمام پیکجز اپنی جگہ پر ہیں، سب سے اہم لائنیں اب بھی وہی ہیں جو پہلے متعارف کرائی گئی تھیں۔ یعنی فیچر پیکج کا pubspec.yaml صرف ان پیکجوں کی فہرست بنائیں جن پر آپ درحقیقت انحصار کر سکتے ہیں۔ feature_home کبھی ظاہر نہیں ہوتا feature_loginایس pubspec.yamlتو feature_login آپ اسے حادثاتی طور پر بھی حاصل نہیں کر سکتے۔ یہ جائزہ لینے والوں کے پکڑے جانے کے بجائے خود ڈارٹ پیکیجنگ سسٹم کے ذریعے نافذ کیا جاتا ہے۔

یہ Flutter کے لیے دستیاب LEGO فن تعمیر کا سب سے طاقتور ورژن ہے۔ ایک اینٹ ایک لفظی پیکیج ہے جو آزادانہ طور پر ورژن ہے، اور ایک جڑنا ایک لفظی پیکیج انحصار ہے جس میں اعلان کیا گیا ہے: pubspec.yamlمرتب کرنے والے، کوڈ کے جائزے نہیں، قوانین کو نافذ کرتے ہیں۔

قابل تبادلہ ریاست کے انتظام کی اینٹیں

ایک اور جدید ترین LEGO اقدام جس کے بارے میں جاننا ضروری ہے وہ یہ ہے کہ ریاستی انتظامی لائبریری کو بھی بدلنے کے قابل اینٹ بنانا ہے۔ یہ اہم ہے اگر آپ کی ٹیم بلاک سے ریور پوڈ میں منتقل ہونا چاہتی ہے، یا منتقلی کے دوران دونوں کو سپورٹ کرنا چاہتی ہے۔

تجاویز وہی ہیں جو اس ہینڈ بک میں استعمال کی گئی ہیں۔ ان معاہدوں کی وضاحت کریں جن پر آپ کا UI انحصار کرتا ہے اور ریاستی انتظام کے دو مختلف نفاذ انہیں مطمئن کرتے ہیں۔

// lib/features/product/presentation/product_presenter.dart
abstract class ProductPresenter {
  ProductUiState get state;
  Stream get stateStream;
  Future load(String id);
}

class ProductUiState {
  final bool isLoading;
  final String? name;
  final String? error;
  const ProductUiState({this.isLoading = false, this.name, this.error});
}

ایک بلاک پر مبنی نفاذ اس طرح نظر آئے گا:

class BlocProductPresenter implements ProductPresenter {
  final ProductCubit _cubit;
  BlocProductPresenter(this._cubit);

  @override
  ProductUiState get state => _mapState(_cubit.state);

  @override
  Stream get stateStream => _cubit.stream.map(_mapState);

  @override
  Future load(String id) => _cubit.load(id);

  ProductUiState _mapState(ProductState s) => switch (s) {
        ProductLoading() => const ProductUiState(isLoading: true),
        ProductLoaded(product: final p) => ProductUiState(name: p.name),
        ProductError(message: final m) => ProductUiState(error: m),
      };
}

یہاں ہم صرف ویجیٹ کی پرت کو درآمد کرتے ہیں۔ ProductPresenter اور ProductUiStateمطلقیت ProductCubit، Blocیا ریور پوڈ کو براہ راست استعمال کریں۔

BlocProductPresenter یہ ایک اڈاپٹر اینٹ ہے جو بلاک میں مخصوص زبانوں کا ترجمہ کرتی ہے۔ ProductState ایک عام شکل میں تشکیل دیا گیا ہے۔ ProductUiState میں ڈارٹ کا استعمال کرتے ہوئے UI کو سمجھتا ہوں۔ switch پیٹرن ملاپ sealed class درجہ بندی جیسا کہ پہلے بیان کیا گیا ہے۔ اگر ٹیم بعد میں ریور پوڈ پر مبنی پیش کنندہ لکھتی ہے تو ویجیٹ کوڈ بالکل تبدیل نہیں ہوگا۔ اس کی وجہ یہ ہے کہ کمپوزیشن روٹ میں صرف وائرنگ تبدیل ہوتی ہے جس پریزینٹر کو ویجیٹ ٹری میں منتقل کیا جاتا ہے۔

یہ LEGO اصول ہے جو سب سے زیادہ غیر مستحکم انحصار پر لاگو ہوتا ہے، کیونکہ ریاستی نظم و نسق کی لائبریری خود ایک اور متبادل اینٹ بن جاتی ہے۔

مکمل کام کرنے والی مثال: اندر صاف تہوں کے ساتھ LEGO طرز کی مصنوعات۔

ہم ہر چیز کو ایک مربوط خصوصیت میں جمع کریں گے، آپ کو فائل کا پورا درخت اور تمام پرزے ایک ساتھ کیسے فٹ ہوتے ہیں دکھاتے ہیں۔

lib/
  core/
    contracts/
      product_lookup.dart          <- shared interface
    di/
      service_locator.dart
    feature_module.dart            <- app-shell contract
  features/
    product/
      product.dart                 <- barrel file / public stud
      product_module.dart          <- implements FeatureModule
      domain/
        entities/product.dart
        repositories/product_repository.dart
        usecases/get_product.dart
      data/
        datasources/product_remote_data_source.dart
        repositories/product_repository_impl.dart
      presentation/
        cubit/product_cubit.dart
        widgets/product_card.dart  <- composed UI bricks

ماڈیول فائلیں تمام سطحوں کو ایک جگہ پر باندھتی ہیں۔

// lib/features/product/product_module.dart
import 'package:dio/dio.dart';
import 'package:go_router/go_router.dart';
import 'package:app/core/feature_module.dart';
import 'package:app/core/di/service_locator.dart';
import 'package:app/core/contracts/product_lookup.dart';
import 'domain/repositories/product_repository.dart';
import 'domain/usecases/get_product.dart';
import 'data/datasources/product_remote_data_source.dart';
import 'data/repositories/product_repository_impl.dart';
import 'presentation/screens/product_screen.dart';

class ProductModule implements FeatureModule {
  @override
  void registerDependencies() {
    getIt.registerLazySingleton(() => Dio());
    getIt.registerLazySingleton(
      () => ProductRemoteDataSource(getIt()),
    );
    getIt.registerLazySingleton(
      () => ProductRepositoryImpl(getIt()),
    );
    // this repository ALSO satisfies the cross-feature ProductLookup
    // contract, so cart (or any other feature) can use it
    // without ever importing anything from this feature's src/.
    getIt.registerLazySingleton(
      () => getIt() as ProductLookup,
    );
    getIt.registerFactory(() => GetProduct(getIt()));
  }

  @override
  List get routes => [
        GoRoute(
          path: '/product/:id',
          builder: (context, state) =>
              ProductScreen(productId: state.pathParameters['id']!),
        ),
      ];
}

یہ ایک فائل بالکل ایک کام (اسمبلی) کرتی ہے۔ تمام منسلک انحصار ایک ہی سمت میں بہتے ہیں، ڈیٹا سے لے کر ڈومین تک پریزنٹیشن تک، پچھلے کلین آرکیٹیکچر ڈایاگرام کے مطابق۔

ایک ہی وقت میں، یہ مندرجہ ذیل کو پورا کرتا ہے: FeatureModule کنفیگریشن فیچر سیکشن میں معاہدہ، یعنی app.dart ہینڈل ProductModule یکساں طور پر CartModule. یہ شامل کرنے کے لئے صرف ایک اور اینٹ ہے۔ modules انوینٹری

قابل توجہ ڈیزائن کے فیصلوں میں شامل ہیں: ProductRepositoryImpl ایک ساتھ دو انٹرفیس لاگو کرتا ہے: مقامی افعال ProductRepositoryیہ خصوصیت اس کے اپنے استعمال کے معاملے میں اور کراس فنکشنل طور پر استعمال ہوتی ہے۔ ProductLookupیہ دیگر خصوصیات کے ذریعہ استعمال کیا جاتا ہے، جیسے کارٹس، جن کے لیے صرف ان کاموں کا ایک ذیلی سیٹ درکار ہوتا ہے جو یہ فیچر انجام دے سکتا ہے۔

یہ ایک عام ایڈوانسڈ LEGO پیٹرن ہے جہاں کنکریٹ کی ایک اینٹ کئی مختلف شکل والے جڑوں کو ظاہر کرتی ہے۔ یہ متعدد صارفین کو ایک دوسرے یا پوری خصوصیت پر انحصار کیے بغیر صرف ان سے متعلقہ سطح کو دیکھنے کی اجازت دیتا ہے۔

ایک آرکیٹیکچر ڈایاگرام جس میں ایک ٹھوس نفاذ دکھایا گیا ہے جو استعمال کے کیس کے ذریعے دو انٹرفیس کو لاگو کرتا ہے: ایک انٹرفیس جو صرف پروڈکٹ کی فعالیت کے اندر استعمال ہوتا ہے اور ایک تنگ انٹرفیس جو دیگر خصوصیات جیسے کہ کارٹ کے استعمال کے لیے سامنے آتا ہے۔

اسے کب استعمال کرنا ہے اور عام نقصانات

مختصر ٹائم لائنز اور چند کاروباری اصولوں والی چھوٹی ایپس کے لیے، ہم LEGO آرکیٹیکچر کی ابتدائی سطح پر رہنے کی تجویز کرتے ہیں۔ ایک ویجیٹ لکھیں، چند معاہدوں کی وضاحت کریں جن کے نفاذ کی آپ توقع کرتے ہیں (مثلاً نیٹ ورک کلائنٹ یا توثیق)، اور اداروں، کیسز اور DTOs کو اس فعالیت پر مجبور نہ کریں جو حقیقت میں فہرست کو ظاہر کرتی ہے اور صارف کو آئٹمز پر ٹیپ کرنے کی اجازت دیتی ہے۔

ایک ہی کوڈبیس، بیرل فائلوں اور پر کام کرنے والے متعدد افراد کی بڑھتی ہوئی ٹیموں کے لیے FeatureModule معاہدہ شروع ہونے سے پہلے خصوصیات کے درمیان ضم ہونے والے تنازعات اور حادثاتی جوڑے کو روکتا ہے۔

پیچیدہ ڈومین منطق کے لیے جسے UI فریم ورک سے باہر رہنے کی ضرورت ہے یا بیک اینڈ ٹیموں کے ذریعے دوبارہ استعمال کیا جا سکتا ہے، ہر فنکشن کے اندر ایک مکمل کلین آرکیٹیکچر پرت متعارف کروانا اپنے آپ میں قیمتی ہو جاتا ہے جب کاروباری اصول اب معمولی نہیں رہ جاتے۔ اس میں رعایت، ٹیکس کے ضوابط، اہلیت کی جانچ، اسٹیٹس سسٹم، اور بہت کچھ شامل ہے۔

ایسے ڈیزائن سسٹمز کے لیے جو آزادانہ طور پر متعدد ٹیموں کے ذریعے جاری کیے جاتے ہیں یا ایپس میں اشتراک کیے جاتے ہیں، یہ ایک ماڈیولر مونوریپو پیٹرن پر جانا سمجھ میں آتا ہے۔ اس کی وجہ یہ ہے کہ خصوصیات اصل پیکیجز بن جاتی ہیں اور مرتب کرنے والا کوڈ کے جائزے پر انحصار کرنے کے بجائے حدود کو نافذ کرتا ہے۔

اس کے ناکام ہونے کے دو طریقے ہیں: پہلا صرف نام میں LEGO ہے، اور فولڈر کا نام ہے: features/cart/تاہم اس کے اندر موجود فائلیں براہ راست پہنچ جاتی ہیں۔ ../../product/src/services/product_repository.dart. جس لمحے ایک فائل دوسرے فنکشن کی بیرل فائل سے گزرتی ہے اور اس فنکشن تک پہنچتی ہے۔ src/آزاد اینٹوں کا اب کوئی وجود نہیں ہے۔ جو باقی رہ گیا ہے وہ فیچر فولڈر کاسٹیوم پہنے ہوئے ایک یک سنگی ہے۔ اصلاح ہمیشہ ایک جیسی ہوتی ہے۔ دوسرے الفاظ میں، یہ معاہدوں کے ذریعے انحصار کو روٹ کرتا ہے۔ core/contracts/.

دوسرا خالص آرکیٹیکچر کارگو کلٹنگ ہے، جہاں حقیقت میں ایک فہرست لینے اور اسے پیش کرنے کی صلاحیت اداروں، اسٹوریج انٹرفیس، اسٹوریج کے نفاذ، ڈی ٹی اوز، استعمال کے کیسز اور کوئبٹس پر ختم ہوتی ہے۔ اسکرین پر 6 فائلیں اور 3 پرتیں ہیں جن میں کوئی حقیقی کاروباری منطق نہیں ہے۔

یہ قطعی طور پر غلط نہیں ہے، لیکن انحصار کے اصولوں کا پورا نکتہ غیر مستحکم کاروباری منطق کو فریم ورک سے بچانا ہے، اور چونکہ یہاں تحفظ کے لیے کوئی کاروباری منطق نہیں ہے، اس لیے یہ ایک ضائع کوشش ہے۔

اگر آپ کے فنکشن میں سرور کی بھیجی ہوئی چیزوں کو ظاہر کرنے کے علاوہ کوئی اصول نہیں ہیں، تو یہ اچھا خیال ہو سکتا ہے کہ اسٹور براہ راست DTO فارم واپس کرے اور ہستی کو چھوڑ کر کیس کے واقعات کا استعمال کرے۔ ان تہوں کو کسی بھی وقت شامل کیا جا سکتا ہے جس وقت حقیقی منطق ظاہر ہوتی ہے، بجائے اس کے کہ اسے قیاس آرائی کے ذریعے بنانے میں وقت ضائع کیا جائے۔

ختم

LEGO آرکیٹیکچر کو اپنے کوڈ کو ترتیب دینے کے طریقے کے طور پر سوچیں، بجائے اس کے کہ آپ انسٹال یا کاپی کریں۔

تعمیر شروع کرنے سے پہلے، آپ سب سے پہلے اس بات کی وضاحت کرتے ہیں کہ آپ اپنی درخواست کے مختلف حصوں کو کس طرح جوڑنا چاہتے ہیں، اس بات کا تعین کرتے ہوئے کہ ہر حصہ کن حصوں پر منحصر ہے اور کن حصوں کو دوسرے حصوں کے سامنے لانا چاہیے۔

ایک بار جب یہ اصول واضح ہو جاتے ہیں، تو ہم ہر جزو کی داخلی تفصیلات کو نجی رکھتے ہوئے، ان کے ارد گرد اصل کلاسز اور عمل درآمد کرتے ہیں تاکہ ایپلیکیشن کے دیگر حصے صرف عوامی انٹرفیس کے ذریعے ہی تعامل کریں۔

آخر میں، یہ کنکریٹ کے ٹکڑوں کو کوڈ بیس پر انحصار پیدا کرنے کے بجائے ایک واضح اسمبلی پوائنٹ پر اکٹھا کرتا ہے۔

کلین آرکیٹیکچر وہ ہے جو آپ کو حاصل ہوتا ہے جب آپ انہی اصولوں کو لاگو کرتے ہیں، ہر خصوصیت کے اندر مخصوص، اچھی طرح سے جانچ شدہ شکلیں (اینٹی، استعمال کیسز اور ریپوزٹری) کا استعمال کرتے ہوئے۔

آج شروع کرنے کے لیے ایک اچھی جگہ مندرجہ ذیل ویجیٹ کی سجاوٹ کی منطق کو اپنے ویجیٹ میں درآمد کرنا ہے۔ SurfaceCard- اسٹائل کا جزو۔ اگلی بار جب آپ سروس کلاس لکھیں تو اسے براہ راست کال کرنے کے بجائے خلاصہ کلاس کو لاگو کرنے کی کوشش کریں۔ اس ہینڈ بک میں باقی سب کچھ - فنکشنل ماڈیولز، سروس لوکیٹر، ماڈیولر مونورپوز، کلین آرکیٹیکچر لیئرز - یہ وہی عادات ہیں جو بڑے پیمانے پر دہرائی جاتی ہیں۔

حوالہ جات

رابرٹ سی مارٹن کا کلین آرکیٹیکچر بلاگ: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

فلٹر کی آفیشل ایپ آرکیٹیکچر گائیڈ: https://docs.flutter.dev/app-architecture

فلٹر کی آرکیٹیکچرل ڈیزائن پیٹرن کی ترکیب: https://docs.flutter.dev/app-architecture/design-patterns

get_it پیکیج دستاویزات: https://pub.dev/packages/get_it

go_router پیکیج دستاویزات: https://pub.dev/packages/go_router

flutter_bloc پیکیج دستاویزات: https://pub.dev/packages/flutter_bloc

dio پیکیج دستاویزات: https://pub.dev/packages/dio

میلوس، ڈارٹ اور فلٹر سنگل ریپوزٹری کے انتظام کے لیے ایک ٹول: https://melos.invertase.dev/

مؤثر ڈارٹ، رسمی انداز اور ساخت کے رہنما خطوط: https://dart.dev/valid-dart

اوپر تک سکرول کریں۔