فلٹر میں مکسنس کا استعمال کیسے کریں۔ [Full Handbook]

ہر فلٹر ڈویلپر کے سفر میں ایک نقطہ ایسا آتا ہے جب وراثت کا ماڈل ٹوٹنا شروع ہو جاتا ہے۔

تم ہو StatefulWidget ان اسکرینوں کے لیے جو متحرک تصاویر چلاتی ہیں۔ اس کے اندر اپنی اینیمیشن منطق کو احتیاط سے لکھیں: SingleTickerProviderStateMixin.

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

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

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

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

کوئی راستہ نہیں StatefulWidget پہلے ہی توسیع Widget. کوئی توسیع بھی نہیں ہے۔ AnimationController یا کوئی اور کلاس۔ زیادہ تر جدید زبانوں کی طرح، ڈارٹ متعدد وراثت کی اجازت نہیں دیتا ہے۔ آپ کو والدین کی ایک کلاس ملتی ہے اور بس۔

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

یہ وہی ہے جو مکسنس کرتے ہیں.

مکسین ڈارٹ کی سب سے طاقتور لیکن کم استعمال شدہ خصوصیات میں سے ایک ہیں۔ پھڑپھڑ خود اسے اپنے فریم ورک میں بڑے پیمانے پر استعمال کرتی ہے۔ TickerProviderStateMixin, AutomaticKeepAliveClientMixin, WidgetsBindingObserverاور بہت سارے مکسنس ہیں۔ ہر بار جب آپ لکھتے ہیں with SingleTickerProviderStateMixin ویجیٹ میں ہم نے اصل میں مکسنس کا استعمال کیا۔

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

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

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

انڈیکس

شرطیں

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

  1. ڈارٹس کی بنیادی باتیں: آپ کو کلاسز، کنسٹرکٹرز، طریقوں، فیلڈز اور وراثت کے تصورات کو سمجھنا چاہیے۔ تم کیا جانتے ہو extends یہ ضروری ہے کہ ڈارٹ ٹائپ سسٹم کیسے کام کرتا ہے۔ کیا آپ نے پہلے اپنی ڈارٹ کلاس کی تعریف کی ہے اور سمجھ لیا ہے کہ یہ کیا ہے؟ super اس کا مطلب ہے کہ آپ تیار ہیں۔

  2. فلٹر ویجیٹ کی بنیادی باتیں: آپ کے درمیان فرق جاننے کی ضرورت ہے۔ StatelessWidget اور StatefulWidgetاور میں یہ سمجھتا ہوں۔ State یہ ایک کلاس ہے جس کا لائف سائیکل ہے۔ initState, build, disposeوغیرہ۔ اس لائف سائیکل کے بارے میں کام کرنے والا علم ضروری ہے کیونکہ فلٹر کے بہت سے اہم مکسنس اس لائف سائیکل سے براہ راست جڑے ہوئے ہیں۔

  3. آبجیکٹ پر مبنی پروگرامنگ کے تصورات: وراثت، انٹرفیس، اور پولیمورفزم کے تصورات سے واقف ہونے سے آپ کو یہ سمجھنے میں مدد ملے گی کہ ان ٹولز کے درمیان ڈیزائن کی جگہ میں مکسنس ایک منفرد اور اہم مقام کیوں رکھتے ہیں۔ آپ کو OOP تھیوریسٹ بننے کی ضرورت نہیں ہے، extends اور implements ڈارٹ میں موازنہ کریں: with یہ اب بہت واضح ہے۔

آپ کو یہ بھی یقینی بنانا چاہئے کہ آپ کے ترقیاتی ماحول میں شامل ہیں:

  • فلٹر SDK 3.x یا اس سے زیادہ

  • Dart SDK 3.x یا اس سے زیادہ (فلٹر کے ساتھ شامل)

  • Flutter پلگ ان کے ساتھ VS Code یا Android Studio جیسا کوڈ ایڈیٹر۔

  • کہ flutter اور dart CLI ٹرمینل سے قابل رسائی

  • ڈارٹ پیڈ (https://dartpad.dev) مکمل پروجیکٹ بنائے بغیر خالص ڈارٹ مکس کی مثالوں کے ساتھ تجربہ کرنے کے لیے خاص طور پر مفید ہے۔

مکسنس استعمال کرنے کے لیے کسی اضافی پیکج کی ضرورت نہیں ہے۔ یہ ایک بلٹ ان ڈارٹ لینگویج فیچر ہے۔ بعد میں اس گائیڈ میں کچھ مثالیں معیاری فلٹر پیکجوں کا استعمال کرتی ہیں، جیسے: flutter_test اس کا مقصد ٹیسٹیبلٹی کا مظاہرہ کرنا ہے، لیکن بنیادی فعالیت کے لیے SDK سے زیادہ کچھ نہیں چاہیے۔

مکس کیا ہے؟

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

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

سرٹیفکیٹ خود کوئی شخص نہیں ہے۔ سرٹیفیکیشن کی خدمات حاصل نہیں کی جا سکتی ہیں۔ لیکن آپ کسی کو سرٹیفکیٹ دے سکتے ہیں، اور اس وقت سے، اس شخص کے پاس وہ تمام صلاحیتیں ہوں گی جن کی سرٹیفکیٹ نمائندگی کرتا ہے۔

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

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

ڈارٹ میں، مکسنس کو استعمال کرتے ہوئے بیان کیا جاتا ہے: mixin کلیدی لفظ یہ فیلڈز اور طریقوں کے ایک سیٹ کی وضاحت کرتا ہے جسے استعمال کرتے ہوئے کلاس میں ملایا جا سکتا ہے: with کلیدی لفظ ایک کلاس جو مکسن کا استعمال کرتی ہے اسے "مکسن” کہا جاتا ہے، اور اس وقت سے، کلاس کو ہر اس چیز تک رسائی حاصل ہوتی ہے جس کی مکسن وضاحت کرتا ہے۔

سب سے آسان مرکب یہ ہے:

mixin Greetable {
  String get name;

  String greet() {
    return 'Hello, my name is $name.';
  }
}

class Person with Greetable {
  @override
  final String name;

  Person(this.name);
}

void main() {
  final person = Person('Ade');
  print(person.greet()); // Hello, my name is Ade.
}

اس کا تجزیہ کیا جائے تو یہ ہے: mixin Greetable ایک نامزد مکسن کا اعلان کریں۔ Greetable. حاصل کرنے والوں پر مشتمل ہے۔ name اور کیسے greet. توجہ فرمائیں name مکسن کے اندر اعلان کیا گیا لیکن اس پر عمل درآمد نہیں ہوا۔

مکسین اس طبقے پر منحصر ہے جو انہیں اپنی اقدار فراہم کرنے کے لیے استعمال کرتا ہے۔ class Person with Greetable مکسچر لگائیں۔ Person. Person نافذ کرنا name مخصوص فیلڈز فراہم کرکے۔ کال کرتے وقت person.greet()ڈارٹ تلاش کرتا ہے: greet اوتار Greetable مکس کریں اور اس کا استعمال کرتے ہوئے چلائیں: Personایس name ایک فیلڈ جو حاصل کرنے والے کے انحصار کو پورا کرتا ہے۔

یہ بنیادی طور پر وراثت سے مختلف ہے۔ Person توسیع نہیں کی Greetable. وہ بچہ نہیں۔ Greetable. مکسین کے افعال درج ذیل ہیں: Personیہ کمپائل کے وقت آپ کی تعریف ہے۔ Person اب بھی بالکل ایک سپر کلاس ہے۔ Object بنیادی طور پر۔

ڈارٹ میں مکسنس کیوں ہوتے ہیں۔

ڈارٹ کو سنگل وراثت کے ساتھ ڈیزائن کیا گیا ہے، یہی انتخاب Java، C#، Swift اور Kotlin میں کیا گیا ہے۔ یہ ڈیزائن معروف متعدد وراثت کے مسائل سے بچتا ہے، خاص طور پر "ہیرے کا مسئلہ”، جہاں دو والدین طبقے ایک ہی طریقہ کی وضاحت کرتے ہیں اور چائلڈ کلاس کے پاس تنازعہ کو حل کرنے کا کوئی واضح طریقہ نہیں ہے۔

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

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

اختلاط سے حل ہونے والا مسئلہ: وراثت کی حدود کو سمجھنا

وراثت کیسے کام کرتی ہے۔

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

پھڑپھڑاہٹ میں، یہ واقف نظر آتا ہے:

class Animal {
  final String name;
  Animal(this.name);

  void breathe() {
    print('$name is breathing.');
  }
}

class Dog extends Animal {
  Dog(super.name);

  void bark() {
    print('$name says: Woof!');
  }
}

Dog وراثت breathe سے Animal اور شامل کریں۔ bark اوپر یہ صاف اور بدیہی ہے، اور جب قسمیں قدرتی طور پر ایک درجہ بندی بناتی ہیں تو اچھی طرح کام کرتی ہے۔

مسئلہ اس وقت شروع ہوتا ہے جب اقسام قدرتی طور پر درجہ بندی نہیں بناتے لیکن پھر بھی رویے کا اشتراک کرتے ہیں۔

سخت طبقاتی مسئلہ

ایک فلٹر ایپ پر غور کریں جس میں درج ذیل کلاسز ہوں: LoginScreen, DashboardScreen, ProfileScreenاور SettingsScreen. وہ سب مختلف اسکرینیں ہیں۔ ان میں سے کسی کو بھی دوسرے کی توسیع نہیں کرنی چاہیے۔ تاہم، آپ کو تجزیہ کے واقعات کو لاگ کرنے کی ضرورت ہے کیونکہ وہ سب ظاہر ہوتے ہیں اور غائب ہو جاتے ہیں۔ ہر ایک کو نیٹ ورک کنکشن کی تبدیلیوں سے نمٹنا پڑتا ہے۔ اور ان میں سے کچھ کو حرکت پذیری کنٹرولر کی ضرورت ہوتی ہے۔

خالص وراثت کے ساتھ آپ کے پاس چند اختیارات ہیں، لیکن وہ سب تکلیف دہ ہیں۔

آپشن 1: ہر چیز کو بیس کلاس میں رکھیں۔

تم ہو BaseScreen توسیع State وہاں تمام مشترکہ طرز عمل کو نافذ کریں۔ تمام اسکرینیں پھیلی ہوئی ہیں۔ BaseScreen.

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

آپشن 2: جامد طریقوں کے ساتھ یوٹیلیٹی کلاسز استعمال کریں۔

آپ تخلیق کرتے ہیں AnalyticsUtil.trackScreen() اسے کسی بھی اسکرین سے دستی طور پر کال کریں۔ initState اور dispose. یہ کام کرتا ہے، لیکن نظم و ضبط اور تکرار کی ضرورت ہے. ہر نئی اسکرین کو تمام افادیت کے طریقوں کو صحیح طریقے سے کال کرنا چاہیے۔ جب تجزیات سے باخبر رہنے کے دستخط تبدیل ہوتے ہیں، تو ہم اسے 30 مقامات پر اپ ڈیٹ کرتے ہیں۔

آپشن 3: کوڈ کاپی اور پیسٹ کریں۔

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

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

ہیرے کا مسئلہ جس سے مکسنس بچتا ہے۔

ایک سے زیادہ وراثت، ایک کلاس کے لیے دونوں والدین کو بیک وقت بڑھانے کی صلاحیت، ایک واضح حل کی طرح لگتا ہے۔ لیکن اس سے ہیروں کا مسئلہ پیدا ہوتا ہے۔

ہیرے کا مسئلہ جس سے مکسنس بچتا ہے۔

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

انٹرفیس فرق

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

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

کور مکسین تصورات: ایک گہری غوطہ

پہلے سے طے شدہ مکسن تعریف

کہ mixin کلیدی لفظ مکسین کا اعلان کرتا ہے۔ اس کے اندر، آپ فیلڈز، طریقے اور حاصل کرنے والے لکھتے ہیں جیسے آپ کلاس کے اندر لکھتے ہیں۔

mixin Logger {
  // A field defined by the mixin.
  // Every class that uses this mixin gets its own _tag field.
  String get tag => runtimeType.toString();

  void log(String message) {
    print('[(tag] )message');
  }

  void logError(String message, [Object? error]) {
    print('[(tag] ERROR: )message');
    if (error != null) print('[(tag] Caused by: )error');
  }
}

یہ mixin بلایا Logger کوڈ کا ایک دوبارہ قابل استعمال ٹکڑا جسے لاگنگ کی فعالیت فراہم کرنے کے لیے کسی بھی کلاس میں شامل کیا جا سکتا ہے۔ یہ خود بخود کلاس کا نام بطور ٹیگ استعمال کرتا ہے اور دو طریقے فراہم کرتا ہے: log عام پیغامات پرنٹ کرنے کے لیے logError غلطی کا پیغام پرنٹ کرتا ہے (اور اختیاری طور پر خود غلطی)۔

اب کوئی بھی کلاس اس لاگنگ فیچر کو منتخب کر سکتی ہے۔

class UserRepository with Logger {
  Future findUser(String id) async {
    log('Looking up user: $id');
    // ...fetch from database...
    return null;
  }
}

class AuthService with Logger {
  Future login(String email, String password) async {
    log('Login attempt for: $email');
    // ...authenticate...
    return true;
  }
}

دونوں UserRepository اور AuthService حاصل کریں log اور logError پیرنٹ کلاس کا اشتراک کیے بغیر طریقے استعمال کریں۔ کہ tag گیٹرز استعمال کریں۔ runtimeType.toString()تو UserRepository ٹیگز کے ساتھ لاگ ان کریں۔ [UserRepository] اور AuthService لاگ [AuthService]وہ سب ایک ہی مکسن کے نفاذ میں پائے جاتے ہیں۔

کہ on مطلوبہ الفاظ: محدود کریں جہاں مکسنس استعمال کیے جاسکتے ہیں۔

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

// This mixin only makes sense on State objects, because it
// uses setState, initState, and dispose which only exist on State.
mixin ConnectivityMixin on State {
  bool _isConnected = true;

  // Because of `on State`, the mixin can freely call setState()
  // and override initState()/dispose() without any errors.
  // These methods are guaranteed to exist on the class using this mixin.

  @override
  void initState() {
    super.initState(); // Must call super when overriding lifecycle methods
    _startConnectivityListener();
  }

  @override
  void dispose() {
    _stopConnectivityListener();
    super.dispose();
  }

  void _startConnectivityListener() {
    // In a real app, subscribe to a connectivity stream here.
    log('Started connectivity monitoring');
    _isConnected = true;
  }

  void _stopConnectivityListener() {
    log('Stopped connectivity monitoring');
  }

  void onConnectivityChanged(bool isConnected) {
    setState(() {
      _isConnected = isConnected;
    });
  }

  bool get isConnected => _isConnected;
}

کہ on State رکوع دو کام کرتا ہے۔ سب سے پہلے، حد. ConnectivityMixin لہذا، ان کو صرف توسیعی کلاسوں کے ساتھ ملایا جا سکتا ہے۔ Stateمرتب وقت پر لاگو ہوتا ہے۔ دوسرا، آپ مکسین کو ہر چیز تک مکمل رسائی دیتے ہیں۔ State ہم فراہم کرتے ہیں: setState, widget, context, mountedاور زندگی کے چکر کے طریقے جیسے: initState اور dispose.

یہاں ہے کہ فلٹر یہ کیسے کرتا ہے: SingleTickerProviderStateMixin فیکٹری یہ استعمال کرتا ہے on State اس بات کو یقینی بنانے کے لیے کہ یہ صرف اس پر لاگو کیا جا سکتا ہے: State ذیلی درجہ بندی اور اوور رائڈ۔ initState اور dispose انتظام کرنے کے لئے Tickerزندگی کا چکر خود بخود انجام پاتا ہے۔

خلاصہ ممبروں کا استعمال کرتے ہوئے مکسنس

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

mixin Validatable {
  // The mixin declares this but does not implement it.
  // Any class using this mixin MUST provide an implementation.
  Map get validators;

  // The mixin provides this using the abstract getter above.
  bool validate(Map formData) {
    for (final entry in validators.entries) {
      final fieldName = entry.key;
      final validatorFn = entry.value;
      final fieldValue = formData[fieldName];
      final error = validatorFn(fieldValue);

      if (error != null) {
        onValidationError(fieldName, error);
        return false;
      }
    }
    return true;
  }

  // Another abstract member -- the class decides how to handle errors.
  void onValidationError(String fieldName, String error);
}

یہ Validatable ایک مکسین دوبارہ قابل استعمال توثیق کے نظام کی وضاحت کرتا ہے جسے کوئی بھی طبقہ اپنا فراہم کر کے اپنا سکتا ہے۔ validators نقشہ اور onValidationError طریقہ استعمال کرتے ہوئے، مکسین خود ہر فیلڈ کے عمل کو سنبھالتا ہے۔ formDataتوثیق کار کو لاگو کریں، پہلی غلطی پر روکیں اور کال کریں. onValidationError اور واپسی پر false توثیق ناکام ہو جاتی ہے یا true اگر سب کچھ گزر جاتا ہے۔

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

class _LoginScreenState extends State with Validatable {
  // Fulfills the mixin's requirement.
  @override
  Map get validators => {
    'email': (value) {
      if (value == null || value.isEmpty) return 'Email is required';
      if (!value.contains('@')) return 'Enter a valid email';
      return null;
    },
    'password': (value) {
      if (value == null || value.isEmpty) return 'Password is required';
      if (value.length < 8) return 'Password must be at least 8 characters';
      return null;
    },
  };

  // Fulfills the other mixin requirement.
  @override
  void onValidationError(String fieldName, String error) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('(fieldName: )error')),
    );
  }

  void _onSubmit() {
    final isValid = validate({
      'email': _emailController.text,
      'password': _passwordController.text,
    });

    if (isValid) {
      // Proceed with login
    }
  }
}

یہ واقعی ایک طاقتور نمونہ ہے۔ کہ Validatable ایک مکسین تمام توثیق آرکیسٹریشن منطق فراہم کرتا ہے لیکن اسے استعمال کرنے والے طبقے کو مخصوص قواعد اور غلطی کی اطلاع دینے کے رویے کو تفویض کرتا ہے۔ مکسین کو تمام فارم اسکرینوں پر دوبارہ استعمال کیا جا سکتا ہے۔ کلاس اپنے رویے کو تجریدی اراکین کے ذریعے اپنی مرضی کے مطابق بناتے ہیں جنہیں وہ نافذ کرتے ہیں۔

ایک سے زیادہ مکسنس کو ملانا

ایک کلاس ایک ساتھ ایک سے زیادہ مکسنس کو اگلی فہرست میں استعمال کر سکتی ہے: withکوما سے الگ:

mixin Analytics {
  void trackEvent(String name, [Map? properties]) {
    print('Analytics: (name ){properties ?? {}}');
  }

  void trackScreenView(String screenName) {
    trackEvent('screen_view', {'screen': screenName});
  }
}

mixin ErrorReporter {
  void reportError(Object error, StackTrace stackTrace) {
    print('Error reported: $error');
    print(stackTrace);
  }
}

mixin Logger {
  String get tag => runtimeType.toString();

  void log(String message) => print('[(tag] )message');
}

// This class uses all three mixins.
class _HomeScreenState extends State
    with Logger, Analytics, ErrorReporter {

  @override
  void initState() {
    super.initState();
    log('HomeScreen initialized');
    trackScreenView('HomeScreen');
  }

  Future _loadData() async {
    try {
      log('Loading data...');
      // ...load data...
    } catch (error, stackTrace) {
      reportError(error, stackTrace);
    }
  }
}

_HomeScreenState حاصل log سے Logger, trackEvent اور trackScreenView سے Analyticsاور reportError سے ErrorReporterسب ایک صاف اعلان میں۔ ان خصوصیات میں سے کسی کو ڈپلیکیٹ کوڈ یا مصنوعی درجہ بندی کو مجبور کرنے کی ضرورت نہیں ہے۔

مکس لائن لائنرائزیشن آرڈر

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

ڈارٹ مکسن لسٹ میں دائیں سے بائیں ایک لکیری زنجیر بناتا ہے۔ اگر آپ کا کلاس ڈیکلریشن اس طرح لگتا ہے:

class MyState extends State
    with MixinA, MixinB, MixinC { ... }

ڈارٹ سلسلہ کو اس طرح حل کرتا ہے:

State -> MixinA -> MixinB -> MixinC -> MyState

Resolution order (most specific wins):
MyState overrides -> MixinC overrides -> MixinB overrides -> MixinA overrides -> State

جب MyState فون کال super.initState()یہ کال کرتا ہے MixinCایس initState. جب MixinC فون کال super.initState()یہ کال کرتا ہے MixinBایس۔ اور اگر آپ زنجیر سے نیچے جاتے ہیں۔ State.

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

// Both mixins override initState. They must both call super.
mixin MixinA on State {
  @override
  void initState() {
    super.initState(); // Calls State's initState
    print('MixinA initialized');
  }
}

mixin MixinB on State {
  @override
  void initState() {
    super.initState(); // Calls MixinA's initState (due to linearization)
    print('MixinB initialized');
  }
}

class MyState extends State with MixinA, MixinB {
  @override
  void initState() {
    super.initState(); // Calls MixinB's initState
    print('MyState initialized');
  }
}

// Output order when MyState is initialized:
// MixinA initialized   (deepest in the chain, runs first)
// MixinB initialized
// MyState initialized  (most specific, runs last)

یہ مثال دکھاتی ہے کہ ڈارٹ مکس کو کس طرح زنجیر پر لگایا جاتا ہے۔ initState فون کال superاس لیے کالز لکیری ترتیب میں کی جاتی ہیں، سب سے زیادہ "بیس” مکسین سے شروع ہوتی ہیں اور اصل کلاس کے ساتھ ختم ہوتی ہیں۔ اس کا مطلب ہے: MixinA اسے پہلے اور پھر چلائیں۔ MixinBاور آخر میں MyStateہر پرت اس کا استعمال کرتے ہوئے اگلی پرت کو کنٹرول منتقل کرتی ہے: super.initState().

لائنرائزیشن چین ویژولائزیشن

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

کہ mixin class اعلان

ڈارٹ 3 متعارف کرایا mixin classہائبرڈ (فوری طور پر) جو سب کو باقاعدہ کلاسوں کے طور پر استعمال کیا جا سکتا ہے۔ new یا توسیع کے لیے بنیاد کے طور پر) اور مکسین کے طور پر (اس کے ساتھ لاگو ہوتا ہے: with)۔ یہ مفید ہے اگر آپ ایسی قسم چاہتے ہیں جو دونوں کرداروں کو بھر سکے۔

// Can be used as `class MyClass extends Serializable` OR
// as `class MyClass with Serializable`
mixin class Serializable {
  Map toJson() {
    // Default implementation -- subclasses or mixers can override
    return {};
  }

  String toJsonString() {
    return toJson().toString();
  }
}

// Used as a mixin
class User with Serializable {
  final String id;
  final String name;

  User({required this.id, required this.name});

  @override
  Map toJson() => {'id': id, 'name': name};
}

// Used as a base class
class Document extends Serializable {
  final String title;

  Document({required this.title});

  @override
  Map toJson() => {'title': title};
}

کہ mixin class فارم عام سے کم عام ہے۔ mixinتاہم، لائبریری API کو ڈیزائن کرتے وقت یہ مفید ہے اور صارفین کے لیے زیادہ سے زیادہ لچک چاہتے ہیں۔

خلاصہ مکس

آپ یہ استعمال کرتے ہوئے براہ راست مکسین کے اندر تجریدی طریقوں کی وضاحت بھی کر سکتے ہیں: abstract آپ کلیدی الفاظ استعمال کرکے یا نفاذ کے بغیر طریقوں کا اعلان کرکے ایسا کرسکتے ہیں۔ استعمال کرنے والے طبقے کو پھر ان ارکان کو لاگو کرنا چاہیے۔

mixin Cacheable {
  // The mixin demands a key from the consuming class.
  String get cacheKey;

  // The mixin demands a TTL (time-to-live) value.
  Duration get cacheTTL;

  // Concrete behavior built on top of the abstract requirements.
  bool isCacheExpired(DateTime cachedAt) {
    return DateTime.now().difference(cachedAt) > cacheTTL;
  }

  String buildVersionedKey(int version) {
    return '({cacheKey}_v)version';
  }
}

class UserProfileCache with Cacheable {
  @override
  String get cacheKey => 'user_profile';

  @override
  Duration get cacheTTL => const Duration(minutes: 5);
}

یہ پیٹرن آپ کی اپنی ایپس میں فریم ورک طرز کا کوڈ بنانے کے لیے بہت مفید ہے۔ ایک مکسین کی وضاحت کریں جو معاہدہ کو نافذ کرتا ہے (عمل درآمد cacheKey اور cacheTTL) دوبارہ قابل استعمال منطق فراہم کرتے ہوئے (عمل درآمد isCacheExpired اور buildVersionedKey) یہ مفت ہے۔

فلٹر کے اپنے فریم ورک سے مکسنس

اپنے مکسنس لکھنے سے پہلے، یہ ضروری ہے کہ ان مکسنز کو سمجھیں جو Flutter پہلے سے فراہم کر رہے ہیں۔ آپ نے تقریباً یقینی طور پر اس کا استعمال کیا ہے لیکن سمجھ گئے ہوں گے کہ اسے مکس کے طور پر کیوں ڈیزائن کیا گیا تھا اور یہ دراصل آپ کے اندر کیا کرتا ہے۔ Stateاپنے جادو منتروں کو سمجھنے میں آسان ٹولز میں تبدیل کریں۔

TickerProviderStateMixin اور SingleTickerProviderStateMixin

فلٹر میں سب سے زیادہ استعمال ہونے والے مکسنس ہیں: SingleTickerProviderStateMixin. فلٹر میں تمام متحرک تصاویر ہیں۔ Tickerایک ایسی چیز ہے جو فی فریم میں ایک بار کال بیک کرتی ہے۔ AnimationController ضرورت TickerProvider (کوئی راستہ نہیں۔ vsync arguments) تو آپ جانتے ہیں کہ ٹک کہاں سے حاصل کرنا ہے۔

SingleTickerProviderStateMixin آپ کا State کلاس ہی TickerProvider. اس کا انتظام انفرادی طور پر کیا جاتا ہے۔ Ticker یہ ویجیٹ کے لائف سائیکل سے جڑا ہوا ہے۔ ٹکرز اس وقت بنتے ہیں جب ریاست کی ابتدا کی جاتی ہے اور ریاست کو تباہ ہونے پر حذف کر دیا جاتا ہے۔ کیونکہ میں استعمال کرتا ہوں۔ on Stateآپ یہ کوڈ کے بغیر کر سکتے ہیں، کچھ کوڈ شامل کرنے کے علاوہ۔ with میں

class _AnimatedCardState extends State
    with SingleTickerProviderStateMixin {

  late AnimationController _controller;
  late Animation _scaleAnimation;

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

    // `this` is passed as vsync because the mixin makes this State
    // object implement the TickerProvider interface.
    _controller = AnimationController(
      vsync: this,           // <-- the mixin makes this valid
      duration: const Duration(milliseconds: 300),
    );

    _scaleAnimation = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.elasticOut),
    );

    _controller.forward();
  }

  @override
  void dispose() {
    _controller.dispose(); // You dispose the controller, the mixin handles the ticker
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ScaleTransition(
      scale: _scaleAnimation,
      child: widget.child,
    );
  }
}

اگر آپ کو ایک سے زیادہ کی ضرورت ہے۔ AnimationController ایک دم Stateآپ استعمال کرتے ہیں TickerProviderStateMixin ("سنگل” کو چھوڑ کر)، آپ لامحدود تعداد میں ٹکر فراہم کر سکتے ہیں۔

class _MultiAnimationState extends State
    with TickerProviderStateMixin {

  late AnimationController _entranceController;
  late AnimationController _pulseController;

  @override
  void initState() {
    super.initState();
    _entranceController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 400),
    );
    _pulseController = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    )..repeat(reverse: true);
  }

  @override
  void dispose() {
    _entranceController.dispose();
    _pulseController.dispose();
    super.dispose();
  }
}

امتیاز اہم ہے۔ SingleTickerProviderStateMixin یہ قدرے زیادہ موثر ہے کیونکہ اندرونی عمل درآمد آسان ہے۔ اس وقت استعمال کریں جب آپ کے پاس بالکل ایک کنٹرولر ہو۔ استعمال کریں TickerProviderStateMixin جب ایک سے زیادہ ہوں۔

AutomaticKeepAliveClientMixin

سکرول کرتے وقت ListView یا PageViewفلٹر میموری کو بچانے کے لیے اسکرین پر سکرول کرتے ہوئے ویجٹ کو ہینڈل کرتا ہے۔ یہ پہلے سے طے شدہ سلوک ہے اور عام طور پر وہی ہوتا ہے جو آپ چاہتے ہیں۔

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

AutomaticKeepAliveClientMixin فلٹر کے زندہ رہنے والے نظام کو مطلع کرتا ہے کہ ویجیٹ کی حالت کو حذف نہیں کیا جانا چاہئے یہاں تک کہ جب ویجیٹ کو اسکرین سے اسکرول کیا جائے۔

class _UserFormState extends State
    with AutomaticKeepAliveClientMixin {

  // This getter is the contract of the mixin. Return true to keep alive.
  // You can make this dynamic if you want conditional keep-alive.
  @override
  bool get wantKeepAlive => true;

  final _nameController = TextEditingController();
  final _emailController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    // CRITICAL: You must call super.build(context) when using this mixin.
    // The mixin's super.build implementation registers this widget with
    // Flutter's keep-alive system. Without this call, the mixin does nothing.
    super.build(context);

    return Column(
      children: [
        TextField(controller: _nameController, decoration: const InputDecoration(labelText: 'Name')),
        TextField(controller: _emailController, decoration: const InputDecoration(labelText: 'Email')),
      ],
    );
  }

  @override
  void dispose() {
    _nameController.dispose();
    _emailController.dispose();
    super.dispose();
  }
}

اس مکسنگ کے لیے دو تقاضے ہیں جنہیں آپ ہمیشہ لاگو کرتے ہیں: wantKeepAlive اور ہمیشہ کال کریں۔ super.build(context). دونوں میں سے کسی ایک کو بھول جانے کا مطلب ہے کہ زندہ رہنے والا رویہ خود بخود کام نہیں کرتا، جس کی تشخیص کرنا ایک مشکل مسئلہ ہے۔

WidgetsBindingObserver

WidgetsBindingObserver اگرچہ تکنیکی طور پر ایک تجریدی طبقے کو مکسین کے طور پر استعمال کیا جاتا ہے (پرانے طرز کے مکسن اپروچ کے ذریعے لاگو کیا جاتا ہے)، لیکن اس کا استعمال مکسین سے مماثل محسوس ہوتا ہے۔ یہ تمہارا ہے State ایپ لائف سائیکل ایونٹس تک رسائی: جب ایپ بیک گراؤنڈ میں چلی جاتی ہے یا پیش منظر میں واپس آتی ہے، جب ڈیوائس کا ٹیکسٹ پیمانہ تبدیل ہوتا ہے، یا جب کسی راستے کو دھکیل دیا جاتا ہے یا پاپ کیا جاتا ہے۔

class _HomeScreenState extends State
    with WidgetsBindingObserver {

  @override
  void initState() {
    super.initState();
    // Register this observer with the global WidgetsBinding.
    // This connects our State to the Flutter framework's event system.
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    // Always deregister before the State is destroyed to prevent
    // callbacks arriving on a disposed State, which causes errors.
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  // Called when the app lifecycle state changes.
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        // App has returned from background. Refresh data if needed.
        _refreshData();
        break;
      case AppLifecycleState.paused:
        // App is going to background. Save draft state, pause timers.
        _saveDraft();
        break;
      case AppLifecycleState.detached:
        // App is being terminated. Final cleanup.
        break;
      default:
        break;
    }
  }

  // Called when the user changes their font size in system settings.
  @override
  void didChangeTextScaleFactor() {
    // Respond to accessibility text size changes if needed.
    setState(() {});
  }

  void _refreshData() {}
  void _saveDraft() {}
}

RestorationMixin

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

class _CounterScreenState extends State
    with RestorationMixin {

  // RestorableInt is a special wrapper that knows how to serialize
  // its value into the restoration bundle.
  final RestorableInt _counter = RestorableInt(0);

  // Required by RestorationMixin: a unique identifier for this state
  // within the restoration hierarchy.
  @override
  String get restorationId => 'counter_screen';

  // Required by RestorationMixin: register all restorable properties here.
  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(_counter, 'counter_value');
  }

  @override
  void dispose() {
    _counter.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text('Counter: ${_counter.value}'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => _counter.value++),
        child: const Icon(Icons.add),
      ),
    );
  }
}

فلٹر مکسین پیٹرن

فلٹر کے تمام بلٹ ان مکسین اسی آرکیٹیکچرل پیٹرن کی پیروی کرتے ہیں جسے آپ اپنے مکسز کو ڈیزائن کرتے وقت نقل کرنا چاہیے۔

وہ استعمال کرتے ہیں on State (یا اسی طرح کی رکاوٹیں) اپنے آپ کو ان کلاسوں تک محدود کرنے کے لیے جو معنی خیز ہیں۔ لائف سائیکل طریقہ کو اوور رائیڈ کریں (initState, dispose, build) خود بخود وسائل کو سیٹ اور ریلیز کرتا ہے، لہذا استعمال کرنے والی کلاسوں کو یوٹیلیٹی فنکشن کو دستی طور پر کال کرنا یاد نہیں رکھنا پڑتا ہے۔ یہ ایک صاف اور کم سے کم API کو بے نقاب کرتا ہے۔ عام طور پر ایک یا دو حاصل کرنے والے یا استعمال کرنے والے طبقے کے ساتھ بات چیت کرنے کے طریقے۔ اور آپ کو تجریدی ممبران کو لاگو کرنے کے لیے استعمال کرنے والے طبقے کی ضرورت ہوگی جو ایک مخصوص سیاق و سباق کے لیے مکسن کے رویے کو اپنی مرضی کے مطابق بنائیں۔

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

آرکیٹیکچر: اپنی فلٹر ایپ پر مکس کیسے لگائیں۔

ایکشن لیئرز کے طور پر مکسنس

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

فلٹر مکسین آرکیٹیکچر پرت

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

ریاستی انتظام کے ساتھ مکس کو ترتیب دینا

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

// The mixin handles analytics -- a cross-cutting concern.
// It knows nothing about business logic.
mixin ScreenAnalytics on State {
  String get screenName;

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

  @override
  void dispose() {
    _trackScreenClosed();
    super.dispose();
  }

  void _trackScreenOpened() {
    AnalyticsService.instance.track('screen_opened', {
      'screen': screenName,
      'timestamp': DateTime.now().toIso8601String(),
    });
  }

  void _trackScreenClosed() {
    AnalyticsService.instance.track('screen_closed', {
      'screen': screenName,
    });
  }

  void trackUserAction(String action, [Map? data]) {
    AnalyticsService.instance.track(action, {
      'screen': screenName,
      ...?data,
    });
  }
}

// The Bloc handles business logic.
// The mixin handles analytics.
// The State class stitches them together cleanly.
class _ProductScreenState extends State
    with ScreenAnalytics {

  @override
  String get screenName => 'ProductScreen';

  late final ProductBloc _bloc;

  @override
  void initState() {
    super.initState();
    // The mixin's initState runs first (due to linearization),
    // tracking the screen open, then this code runs.
    _bloc = ProductBloc()..add(LoadProduct(widget.productId));
  }

  void _onAddToCart(Product product) {
    _bloc.add(AddToCart(product));
    // Use the mixin's method to track this action.
    trackUserAction('add_to_cart', {'product_id': product.id});
  }
}

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

اپنے مکسنس لکھنا: عملی نمونے۔

لائف سائیکل مکسین پیٹرن

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

یہاں ایک دوبارہ قابل استعمال مکس ہے: TextEditingController:

mixin TextControllerMixin on State {
  // The consuming class provides the number of controllers needed.
  // This makes the mixin flexible without hardcoding behavior.
  List get textControllers;

  @override
  void dispose() {
    // Automatically disposes every controller the class declared.
    // The class never needs to remember to call dispose() on each one.
    for (final controller in textControllers) {
      controller.dispose();
    }
    super.dispose();
  }
}

// Usage: the State class simply declares its controllers and mixes in the mixin.
// Disposal is handled automatically -- no manual dispose calls needed.
class _RegistrationFormState extends State
    with TextControllerMixin {

  final _nameController = TextEditingController();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  List get textControllers => [
    _nameController,
    _emailController,
    _passwordController,
  ];

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(controller: _nameController),
        TextField(controller: _emailController),
        TextField(controller: _passwordController),
      ],
    );
  }
}

یہاں کی طاقت ہے۔ _RegistrationFormState اپنے کنٹرولر کو ضائع کرنا نہ بھولیں۔ مکسنز خود بخود تصرف کو یقینی بناتا ہے۔

ڈیباؤنس مکسین پیٹرن

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

mixin DebounceMixin on State {
  Timer? _debounceTimer;

  // Runs `action` after `delay` has passed without another call.
  // Each new call resets the timer.
  void debounce(VoidCallback action, {Duration delay = const Duration(milliseconds: 500)}) {
    _debounceTimer?.cancel();
    _debounceTimer = Timer(delay, action);
  }

  @override
  void dispose() {
    _debounceTimer?.cancel();
    super.dispose();
  }
}

// Any screen that needs debounced search gets it for free.
class _SearchScreenState extends State
    with DebounceMixin {

  void _onSearchChanged(String query) {
    // This fires 500ms after the user stops typing, not on every keystroke.
    debounce(() {
      context.read().add(SearchQueryChanged(query));
    });
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      onChanged: _onSearchChanged,
      decoration: const InputDecoration(hintText: 'Search...'),
    );
  }
}

سٹیٹ مکسن پیٹرن لوڈ ہو رہا ہے۔

بہت ساری اسکرینیں ایک ہی ساخت کا اشتراک کرتی ہیں۔ یعنی، یہ لوڈ کی حالت، غلطی کی حالت، یا ڈیٹا کی حالت ہوسکتی ہے۔ تمام اسکرینوں پر ان تینوں ریاستوں کا دستی طور پر انتظام کرنا تکرار کا باعث بنتا ہے۔ ایک مکسین اس کو معیاری بنا سکتا ہے:

mixin LoadingStateMixin on State {
  bool _isLoading = false;
  Object? _error;

  bool get isLoading => _isLoading;
  bool get hasError => _error != null;
  Object? get error => _error;

  // Wraps an async operation with automatic loading state management.
  // The consuming class calls this instead of managing booleans manually.
  Future runWithLoading(Future Function() operation) async {
    if (_isLoading) return null; // Prevent duplicate calls

    setState(() {
      _isLoading = true;
      _error = null;
    });

    try {
      final result = await operation();
      if (mounted) {
        setState(() => _isLoading = false);
      }
      return result;
    } catch (e) {
      if (mounted) {
        setState(() {
          _isLoading = false;
          _error = e;
        });
      }
      return null;
    }
  }

  void clearError() {
    setState(() => _error = null);
  }
}

// Any data-fetching screen gets this for free.
class _ProfileScreenState extends State
    with LoadingStateMixin {

  User? _user;

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

  Future _fetchUser() async {
    final user = await runWithLoading(
      () => UserRepository().getUser(widget.userId),
    );
    if (user != null && mounted) {
      setState(() => _user = user);
    }
  }

  @override
  Widget build(BuildContext context) {
    if (isLoading) {
      return const Center(child: CircularProgressIndicator());
    }

    if (hasError) {
      return Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('Error: $error'),
            ElevatedButton(
              onPressed: () {
                clearError();
                _fetchUser();
              },
              child: const Text('Retry'),
            ),
          ],
        ),
      );
    }

    if (_user == null) {
      return const Center(child: Text('No user found.'));
    }

    return ProfileView(user: _user!);
  }
}

یہ مرکب ہے، LoadingStateMixinایک بلٹ ان طریقہ شامل کریں۔ State ایک کلاس جو بوائلر پلیٹ کو دہرائے بغیر لوڈنگ، غلطیوں اور غیر مطابقت پذیر کارروائیوں کو سنبھالتی ہے۔ یہ بے نقاب کرکے کرتا ہے۔ isLoading, hasErrorاور error حاصل کرنے والا، اور runWithLoading کامیابیوں اور غلطیوں کو محفوظ طریقے سے سنبھالتے ہوئے لوڈنگ کو خود بخود آن اور آف کرنے کا ایک طریقہ۔ پھر مندرجہ ذیل سکرین ظاہر ہو جائے گا: _ProfileScreenState آپ آسانی سے کال کر سکتے ہیں۔ runWithLoading ڈیٹا بازیافت کرتے وقت، UI لوڈر، غلطی کا پیغام، یا اصل مواد کو ظاہر کرنے کے لیے فراہم کردہ اسٹیٹس ویلیو کا استعمال کرتا ہے۔

فارم کی توثیق مکسین پیٹرن

فارم کی توثیق کی منطق تقریباً تمام ایپس میں عالمگیر ہے۔ تمام رجسٹریشن اسکرینز، لاگ ان اسکرینز، اور سیٹ اپ اسکرینز جمع کرانے سے پہلے آپ کے ان پٹ کی توثیق کرتی ہیں۔

پروڈکشن کے لیے تیار توثیق مکسین یہ ہے:

mixin FormValidationMixin on State {
  final _formKey = GlobalKey();
  final Map _fieldErrors = {};

  GlobalKey get formKey => _formKey;
  Map get fieldErrors => Map.unmodifiable(_fieldErrors);

  bool validateForm() {
    // Clears all previous field errors
    setState(() => _fieldErrors.clear());

    final isFormValid = _formKey.currentState?.validate() ?? false;

    if (!isFormValid) {
      onValidationFailed();
    }

    return isFormValid;
  }

  void setFieldError(String field, String? error) {
    setState(() => _fieldErrors[field] = error);
  }

  String? getFieldError(String field) => _fieldErrors[field];

  bool get hasAnyError => _fieldErrors.values.any((e) => e != null);

  // Called when form validation fails. The class can override this
  // to show a snackbar, scroll to the first error, or play a shake animation.
  void onValidationFailed() {}
}

یہ FormValidationMixin کچھ بھی دو State فارم کی توثیق کا انتظام کرنے کے لیے بلٹ ان طریقے فراہم کرنے کے لیے کلاسز فراہم کرتا ہے۔ formKey اپنے فارم کو کنٹرول کریں، فیلڈ لیول کی غلطیوں کو اسٹور کریں اور ان کو بے نقاب کریں، اور اس کے ذریعے توثیق چلائیں: validateFormناکامی پر اپنی کلاس کا رد عمل بذریعہ کریں: onValidationFailed. یہ دستی غلطی کو ترتیب دینے اور غلطیوں کی جانچ کرنے، UI کو صاف رکھنے اور توثیق کی منطق کو دہرانے کی بجائے سنٹرلائز کرنے کی بھی اجازت دیتا ہے۔

اعلی درجے کے تصورات

مکسین بمقابلہ خلاصہ کلاس بمقابلہ توسیع کا طریقہ

دیگر ڈارٹ ٹولز کے مقابلے میں مکسین کا استعمال کب کرنا ہے یہ سمجھنا اتنا ہی ضروری ہے جتنا یہ جاننا کہ مکسین کیسے لکھنا ہے۔ ہر ٹول کا اپنا مقصد ہوتا ہے۔

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

"is-a” تعلقات کی ماڈلنگ کرتے وقت تجریدی کلاسز کا استعمال کریں۔ Dog چاندی Animal, PaymentCard ہے PaymentMethod. جب قسم کی شناخت اہم ہو اور آپ لکھنا چاہیں تو آپ خلاصہ کلاسز بھی استعمال کر سکتے ہیں: if (payment is PaymentMethod).

مکس سپر کلاس سلاٹس کا استعمال کیے بغیر طرز عمل کے دوبارہ قابل استعمال بنڈلز کی وضاحت کریں۔

"is” یا "ممکنہ” تعلقات کی ماڈلنگ کرتے وقت مکسنس کا استعمال کریں۔ اسکرین کہتی ہے "ہیز اینالیٹکس ٹریکنگ ہے”، ریپوزٹری کہتی ہے "لاگنگ کو قابل بناتا ہے”، اور فارم کہتا ہے "توثیق ہے”۔ مکسینز کراس فنکشنلٹی کے لیے ہیں جو کلاس کی بنیادی شناخت کی وضاحت نہیں کرتی ہیں۔

توسیع کا طریقہ طریقوں میں ترمیم یا ذیلی کلاس کیے بغیر موجودہ قسم میں طریقے شامل کریں۔

ان اقسام میں افادیت کے طریقے شامل کرنے کے لیے ایکسٹینشنز کا استعمال کریں جو آپ کے پاس نہیں ہیں۔ toFormatted() کو DateTimeیا capitalize() کو String. ایکسٹینشنز فیلڈز کو شامل یا موجودہ طریقوں کو اوور رائڈ نہیں کر سکتی ہیں۔

// Abstract class: modeling type identity
abstract class Shape {
  double get area; // Contract
  double get perimeter; // Contract

  String describe() => 'A ({runtimeType} with area ){area.toStringAsFixed(2)}';
}

class Circle extends Shape {
  final double radius;
  Circle(this.radius);

  @override double get area => 3.14159 * radius * radius;
  @override double get perimeter => 2 * 3.14159 * radius;
}

// Mixin: adding behavior without changing identity
mixin Drawable {
  void draw(Canvas canvas) {
    // Default drawing logic
  }
}

// Extension method: utility on an existing type
extension DateTimeFormatting on DateTime {
  String get relativeLabel {
    final diff = DateTime.now().difference(this);
    if (diff.inDays > 0) return '${diff.inDays}d ago';
    if (diff.inHours > 0) return '${diff.inHours}h ago';
    return '${diff.inMinutes}m ago';
  }
}

یہ کوڈ ڈارٹ میں رویے کو بڑھانے یا ڈھانچے کے تین طریقے دکھاتا ہے۔

  • خلاصہ کلاس (Shape) ایک معاہدے کی وضاحت کرتا ہے جس کی تمام شکلیں شیئرنگ فراہم کرتے وقت عمل کرتی ہیں۔ describe طریقہ

  • ایک ہی کلاس Circle اس معاہدے کو اپنی منطق سے نافذ کریں۔ area اور perimeter

  • مکس (Drawable) اور درج ذیل دوبارہ قابل استعمال اعمال شامل کریں: draw آپ کسی بھی کلاس سے اس کی ID تبدیل کیے بغیر جڑ سکتے ہیں۔

  • اور توسیع (DateTimeFormatting) ایک مددگار طریقہ شامل کریں۔ relativeLabel کو DateTime ٹائپ کرکے، آپ اصل کلاس میں ترمیم کیے بغیر "2 گھنٹے پہلے” جیسے انسان دوست ٹائم لیبل آسانی سے حاصل کر سکتے ہیں۔

مکسنس اور انٹرفیس کو ایک ساتھ استعمال کرنا

مکسنس اور implements ہم مل کر طاقتور طریقے سے کام کر سکتے ہیں۔ آپ کے پاس ایک مکسین ہوسکتا ہے جو انٹرفیس کا ڈیفالٹ نفاذ فراہم کرتا ہے جبکہ استعمال کرنے والی کلاس کو پولیمورفک ہونے کی بھی اجازت دیتا ہے۔

abstract interface class Disposable {
  void dispose();
}

// The mixin provides a real implementation of dispose.
// Classes using this mixin satisfy the Disposable interface.
mixin AutoDispose implements Disposable {
  final List _subscriptions = [];
  final List _timers = [];

  void addSubscription(StreamSubscription subscription) {
    _subscriptions.add(subscription);
  }

  void addTimer(Timer timer) {
    _timers.add(timer);
  }

  @override
  void dispose() {
    for (final sub in _subscriptions) {
      sub.cancel();
    }
    for (final timer in _timers) {
      timer.cancel();
    }
    _subscriptions.clear();
    _timers.clear();
  }
}

class DataService with AutoDispose {
  DataService() {
    // Register resources. They will all be cleaned up when dispose() is called.
    addSubscription(
      someStream.listen((data) => handleData(data)),
    );
    addTimer(
      Timer.periodic(const Duration(minutes: 1), (_) => refresh()),
    );
  }
}

// This works because AutoDispose implements Disposable.
void cleanUp(Disposable resource) {
  resource.dispose();
}

یہ کوڈ Disposable ایک انٹرفیس جس کی ضرورت ہے۔ dispose طریقہ فراہم کرتا ہے۔ AutoDispose ایک مکسین جو اسے ٹریک کرکے اور خودکار طور پر سبسکرپشنز اور ٹائمرز کو منظم کرکے لاگو کرتا ہے۔

تو کوئی بھی کلاس DataService مکسنس کا استعمال کرتے ہوئے آپ وسائل کو رجسٹر کرسکتے ہیں۔ addSubscription اور addTimer اگلی بار، ہر چیز کو محفوظ طریقے سے ضائع کریں۔ dispose کہا جاتا ہے، لیکن پھر بھی کہیں بھی استعمال کیا جا سکتا ہے۔ Disposable یہ متوقع ہے۔

تنہائی میں مکسنس کی جانچ کریں۔

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

// test/mixins/loading_state_mixin_test.dart

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

// A minimal fake State that uses the mixin -- no real widget needed.
class TestLoadingState extends State
    with LoadingStateMixin {
  @override
  Widget build(BuildContext context) => const SizedBox();
}

void main() {
  group('LoadingStateMixin', () {
    testWidgets('starts in non-loading state', (tester) async {
      final state = TestLoadingState();

      expect(state.isLoading, false);
      expect(state.hasError, false);
      expect(state.error, null);
    });

    testWidgets('sets loading true during operation', (tester) async {
      await tester.pumpWidget(
        MaterialApp(home: StatefulBuilder(
          builder: (context, setState) {
            return const SizedBox();
          },
        )),
      );

      // Test the mixin behavior through the widget test infrastructure
      // ...
    });

    test('debounce mixin cancels previous timers', () async {
      // Pure Dart test -- no widget infrastructure needed
      int callCount = 0;

      // Test debounce behavior
      // ...
    });
  });
}

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

خالص ڈارٹ مکسنس (ریاستی کے بجائے) کے لیے، جانچ بہت آسان ہے کیونکہ فلٹر ویجیٹ کے بنیادی ڈھانچے کی بالکل بھی ضرورت نہیں ہے۔

// A pure Dart mixin with no Flutter dependency
mixin Serializable {
  Map toJson();

  String toJsonString() => toJson().toString();

  bool isEquivalentTo(Serializable other) {
    return toJson().toString() == other.toJson().toString();
  }
}

// Test it with a plain Dart test
class TestModel with Serializable {
  final String name;
  TestModel(this.name);

  @override
  Map toJson() => {'name': name};
}

void main() {
  test('Serializable.isEquivalentTo compares correctly', () {
    final a = TestModel('Ade');
    final b = TestModel('Ade');
    final c = TestModel('Chioma');

    expect(a.isEquivalentTo(b), true);
    expect(a.isEquivalentTo(c), false);
  });
}

یہ کوڈ درج ذیل خالص ڈارٹ مکسین کی وضاحت کرتا ہے: Serializable اسے استعمال کرنے والی تمام کلاسوں کو اسے نافذ کرنے کی ضرورت ہوگی۔ toJson. اس کے بعد یہ مددگار طریقے فراہم کرتا ہے جو اس ڈیٹا کو سٹرنگ میں تبدیل کرتے ہیں اور ان کی JSON نمائندگی میں دو اشیاء کا موازنہ کرتے ہیں۔ یہ چیک کرنے کا ایک آسان طریقہ فراہم کرتا ہے کہ آیا دو اشیاء ایک جیسی ہیں۔

کہ TestModel کلاس یہ ظاہر کرتی ہے کہ اس کو لاگو کرکے یہ کیسے کام کرتا ہے۔ toJsonٹیسٹوں کے ذریعے جو اس بات کو یقینی بناتے ہیں کہ ایک جیسے ڈیٹا والی اشیاء کو مساوی سمجھا جاتا ہے اور مختلف ڈیٹا والی اشیاء کو نہیں سمجھا جاتا ہے۔

کارکردگی کے تحفظات

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

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

حقیقی ایپس کے لیے بہترین طریقے

ایک مرکب، ایک تشویش

مکسین ڈیزائن کا سب سے اہم اصول یہ ہے کہ ہر مکس کی بالکل ایک ذمہ داری ہونی چاہیے۔ جس کا نام مکسین ہے۔ ScreenBehavior یہ کوئی مکس نہیں ہے جو تجزیہ، کنیکٹیویٹی، لاگنگ اور توثیق کو سنبھالتا ہے۔ یہ ایک الہی ہستی ہے جو ملبوس لباس پہنتی ہے۔

اگر آپ موجودہ مکسن میں غیر متعلقہ طریقے شامل کر رہے ہیں، تو یہ اسے تقسیم کرنے کا اشارہ ہے۔

// Wrong: one mixin doing too much
mixin ScreenBehavior on State {
  void trackEvent(String name) { /* ... */ }     // analytics
  bool get isConnected { /* ... */ }             // connectivity
  void log(String msg) { /* ... */ }             // logging
  bool validateEmail(String e) { /* ... */ }     // validation
  void showSnackBar(String msg) { /* ... */ }    // UI interaction
}

// Right: each concern is its own mixin
mixin ScreenAnalytics on State {
  void trackEvent(String name) { /* ... */ }
}

mixin ConnectivityAware on State {
  bool get isConnected { /* ... */ }
}

mixin Logger {
  void log(String msg) { /* ... */ }
}

اس مثال میں، پہلا مرکب ہے ScreenBehaviorآپ بہت زیادہ غیر متعلقہ کام کر رہے ہیں: تجزیات، کنیکٹیویٹی، لاگنگ، توثیق، UI کام وغیرہ۔ اس سے دیکھ بھال اور دوبارہ استعمال مشکل ہو جاتا ہے۔

ایک بہتر طریقہ یہ ہے کہ ہر ذمہ داری کو اس کے اپنے مرکوز مرکب میں تقسیم کیا جائے: ScreenAnalytics, ConnectivityAwareاور Loggerلہٰذا، ہر مکس کا ایک ہی مقصد ہوتا ہے اور صرف ضرورت کے وقت ہی اسے صاف ستھرا بنایا جا سکتا ہے۔

لائف سائیکل طریقوں میں ہمیشہ سپر کو کال کریں۔

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

mixin SomeMixin on State {
  @override
  void initState() {
    super.initState(); // ALWAYS call super, and ALWAYS call it before your code
    // Your setup code here
  }

  @override
  void dispose() {
    // Your cleanup code here
    super.dispose(); // In dispose, call super LAST, after your cleanup
  }
}

پھڑپھڑانے کے اصول درج ذیل ہیں: initStateکال super پہلے میں disposeکال super آخری یہ طریقہ کار کی عکاسی کرتا ہے۔ State یہ اپنے طور پر کام کرتا ہے اور اس بات کو یقینی بناتا ہے کہ وسائل کو استعمال کرنے اور صاف کرنے سے پہلے ان کے بنیادی وسائل کو ختم کرنے سے پہلے ترتیب دیا گیا ہے۔

مکسین پروجیکٹ کا ڈھانچہ

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

lib/
  mixins/
    analytics_mixin.dart        -- Screen analytics tracking
    connectivity_mixin.dart     -- Network state monitoring
    debounce_mixin.dart         -- Input debouncing
    form_validation_mixin.dart  -- Form validation orchestration
    loading_state_mixin.dart    -- Loading/error/data state management
    logger_mixin.dart           -- Structured logging
    lifecycle_logger_mixin.dart -- Logs initState and dispose calls

  screens/
    home/
      home_screen.dart          -- Uses analytics + connectivity + logger
    search/
      search_screen.dart        -- Uses debounce + loading state
    settings/
      settings_screen.dart      -- Uses form validation + loading state

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

صارفین کے بجائے فنکشن کی بنیاد پر ناموں کو ملانا

مکسین کسی مخصوص صارف کے بجائے فعالیت یا رویے کی وضاحت کرتے ہیں۔ اس کے مطابق نام رکھیں۔

// Wrong: names tied to a specific consumer
mixin HomeScreenAnalytics { }
mixin LoginFormValidation { }
mixin DashboardConnectivity { }

// Right: names describe the capability
mixin ScreenAnalytics { }
mixin FormValidation { }
mixin ConnectivityAware { }

فنکشن کے نام والے مکسنز قدرتی طور پر اس وقت پائے جاتے ہیں جب ڈویلپرز "کیا ایسے کوئی مکسین ہیں جو تجزیاتی ٹریکنگ فراہم کرتے ہیں؟” اسکرین کے ناموں کے ساتھ مکسنس اس طرح دریافت نہیں ہوتے ہیں۔

معاہدے کی دستاویزات

وہ مکس جو تجریدی ارکان کا استعمال کرتے ہیں یا استعمال کرنے والی کلاسوں پر تقاضے عائد کرتے ہیں ان ضروریات کو واضح طور پر دستاویز کرنا چاہیے۔ مکسین لگانے والے ڈویلپرز کو یہ جاننے کی ضرورت ہے کہ وہ کس چیز کو لاگو کرنے پر راضی ہیں۔

/// A mixin that tracks screen analytics automatically.
///
/// Usage:
/// ```dart
/// class _MyScreenState extends State
///     with ScreenAnalyticsMixin {
///   @override
///   String get screenName => 'MyScreen';
/// }
/// ```
///
/// Requires:
/// - [screenName]: A stable, unique identifier for this screen.
///   Used as the event property in all analytics calls.
///
/// Provides:
/// - Automatic `screen_opened` event on initState.
/// - Automatic `screen_closed` event on dispose.
/// - [trackAction]: Manual event tracking for user interactions.
mixin ScreenAnalyticsMixin on State {
  String get screenName;

  @override
  void initState() {
    super.initState();
    _track('screen_opened');
  }

  @override
  void dispose() {
    _track('screen_closed');
    super.dispose();
  }

  void trackAction(String action, [Map? data]) {
    _track(action, data);
  }

  void _track(String event, [Map? data]) {
    AnalyticsService.instance.track(event, {
      'screen': screenName,
      ...?data,
    });
  }
}

مکس کب استعمال کریں اور کب نہ استعمال کریں۔

جہاں مکسنس چمکتے ہیں۔

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

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

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

دوبارہ قابل استعمال وسائل کا انتظام استعمال کا ایک اور طاقتور معاملہ ہے۔ تمام وسائل جن کو آگے بنانے کی ضرورت ہے۔ initState اور اس میں تباہ ہو گیا۔ dispose مکسین کے امیدواروں میں اینیمیشن کنٹرولرز، اسٹریم سبسکرپشنز، ٹائمرز، فوکس نوڈس، اور اسکرول کنٹرولرز شامل ہیں۔ ان میں سے ہر ایک مرکب ہے جس کے لکھے جانے کا انتظار ہے۔

جب مکسنس غلط ٹول ہیں۔

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

اگر مطلوبہ رویہ حقیقت میں آبجیکٹ کی سطح پر ہے، یعنی آپ رویے کی ایک مثال بنا کر اسے پاس کرنا چاہتے ہیں تو مکسین بھی ایک برا انتخاب ہے۔ اگر آپ لکھنا چاہتے ہیں تو final handler = SomeHandler() اور اسے انحصار کے طور پر انجیکشن کریں۔ یہ ایک کلاس ہے، مکس نہیں ہے۔ مکسز کو فوری نہیں کیا جا سکتا۔

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

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

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

عام غلطیاں

فراموشی super لائف سائیکل کی نئی تعریف

یہ سب سے عام مکسن بگ ہے اور ٹھیک ٹھیک ہے کیونکہ یہ ہمیشہ فوری حادثے کا سبب نہیں بنتا۔ خود بخود مکس چینز کو توڑ دیتا ہے۔

// BROKEN: forgetting super.initState() in a mixin
mixin BrokenMixin on State {
  @override
  void initState() {
    // super.initState() is missing.
    // Any other mixin in the chain behind this one will NEVER have
    // its initState() called. Their setup code is silently skipped.
    _setupSomething();
  }
}

// CORRECT: always call super
mixin CorrectMixin on State {
  @override
  void initState() {
    super.initState(); // Chain continues to the next mixin and State
    _setupSomething();
  }
}

قوانین مطلق ہیں۔ اگر آپ کا مکسن لائف سائیکل طریقہ کو اوور رائیڈ کرتا ہے، تو آپ کو کال کرنا چاہیے: super. کوئی مستثنیات نہیں ہیں.

مکسچر کے بغیر مکسن لگانا on ریاست پر پابندیاں

کچھ مکسنس خاص طور پر ان کے لیے ڈیزائن کیے گئے ہیں: State اعتراض، استعمال setState, mounted, contextیا لائف سائیکل کا طریقہ۔ ان مکسنس کو اسٹیٹ کلاس کے علاوہ کسی اور کلاس پر لاگو کرنے کے نتیجے میں تالیف کی خرابی ہوگی۔

لیکن ایک زیادہ ہوشیار ورژن ایک مکسین لکھنا ہوگا جو استعمال کرتا ہے: setState اعلان کیے بغیر on State زبردستی رکاوٹوں کے بغیر، ڈارٹ اس بات کی ضمانت نہیں دیتا: setState یہ استعمال کرنے والے طبقے میں موجود ہے اور مبہم غلطیوں کے ساتھ تالیف کو ناکام بنا سکتا ہے۔

// WRONG: uses setState without declaring the constraint
mixin BrokenLoadingMixin {
  bool _isLoading = false;

  void startLoading() {
    setState(() => _isLoading = true); // ERROR: setState is not defined here
  }
}

// CORRECT: declare what types this mixin requires
mixin LoadingMixin on State {
  bool _isLoading = false;

  void startLoading() {
    setState(() => _isLoading = true); // Works: State guarantees setState
  }
}

فراموشی super.build کو AutomaticKeepAliveClientMixin

AutomaticKeepAliveClientMixin یہ فلٹر مکسنس میں منفرد ہے کہ اسے کال کی ضرورت ہوتی ہے۔ super.build(context) آپ کے اندر build طریقہ ایسا کرنا بھول جانے کا مطلب ہے کہ زندہ رکھنے کا طریقہ کار کبھی بھی فعال نہیں ہوگا اور ویجیٹ کو خوبصورتی سے حذف کر دیا جائے گا، خود بخود مکسین کے پورے مقصد کو شکست دے گا۔

// WRONG: forgets super.build -- keep-alive never activates
class _BrokenState extends State
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    // Missing: super.build(context)
    return const Placeholder();
  }
}

// CORRECT: always call super.build when using this mixin
class _CorrectState extends State
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context); // Registers this widget with the keep-alive system
    return const Placeholder();
  }
}

مکسنس کو خدا کی اشیاء کے طور پر استعمال کرنا

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

// WRONG: one mixin handling too many unrelated concerns
mixin AppBehaviorMixin on State {
  // Analytics
  void trackEvent(String name) { }

  // Connectivity
  bool get isConnected { return true; }

  // Logging
  void log(String message) { }

  // Form validation
  bool validateEmail(String email) { return true; }

  // Snackbar management
  void showSuccessSnackBar(String message) { }
  void showErrorSnackBar(String message) { }

  // Loading state
  bool get isLoading { return false; }

  // Navigation
  void navigateToHome() { }
}

// CORRECT: separate concerns into focused mixins
mixin ScreenAnalytics on State { /* ... */ }
mixin ConnectivityAware on State { /* ... */ }
mixin Logger { /* ... */ }
mixin SnackBarHelper on State { /* ... */ }
mixin LoadingStateMixin on State { /* ... */ }

دستاویزات کے بغیر مخلوط آرڈر انحصار

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

// These two mixins both override initState.
// Their order in the `with` clause determines which runs first.
// Document this clearly so future developers do not accidentally swap them.

/// IMPORTANT: LoggerMixin must come BEFORE AnalyticsMixin in the `with` clause.
/// LoggerMixin sets up the logging infrastructure that AnalyticsMixin relies on.
///
/// Correct:   with LoggerMixin, AnalyticsMixin
/// Incorrect: with AnalyticsMixin, LoggerMixin
mixin AnalyticsMixin on State {
  @override
  void initState() {
    super.initState();
    // By the time this runs, LoggerMixin has already run (it was before us),
    // so log() is ready to use.
    log('Analytics initialized for ${runtimeType}');
    _trackScreenOpen();
  }
}

منی اینڈ ٹو اینڈ مثال

آئیے ایک مکمل، کام کرنے والی فلٹر اسکرین بنائیں جو ایک مربوط مثال میں مکس کے تمام بنیادی تصورات کو ظاہر کرتی ہے۔ ہم ہیں SearchScreen یہ تین حسب ضرورت مکسنس کے ساتھ فلٹر کی بلٹ ان خصوصیات کا استعمال کرتا ہے: ایک لاگنگ کے لیے، ایک ڈیباؤنس ان پٹ کے لیے، اور ایک اسٹیٹ مینجمنٹ کو لوڈ کرنے کے لیے۔ AutomaticKeepAliveClientMixin پورے ٹیب نیویگیشن میں حالت کو برقرار رکھتا ہے۔

مکس

// lib/mixins/logger_mixin.dart

/// Provides structured logging with automatic class name tagging.
/// This mixin has no Flutter dependency and can be applied to any class.
mixin LoggerMixin {
  String get tag => runtimeType.toString();

  void log(String message) {
    // In production, replace with your logging framework (e.g., logger package).
    debugPrint('[(tag] )message');
  }

  void logError(String message, [Object? error, StackTrace? stackTrace]) {
    debugPrint('[(tag] ERROR: )message');
    if (error != null) debugPrint('[(tag] Caused by: )error');
    if (stackTrace != null) debugPrint(stackTrace.toString());
  }
}

// lib/mixins/debounce_mixin.dart

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

/// Provides debounced callback execution for State classes.
/// Automatically cancels the pending timer on dispose.
///
/// Requires: must be applied to a State object.
///
/// Provides:
/// - [debounce]: delays an action until input has stopped for [delay] duration.
mixin DebounceMixin on State {
  Timer? _debounceTimer;

  /// Delays [action] by [delay]. Resets the delay on every new call.
  /// Useful for responding to text field changes without firing on every keystroke.
  void debounce(
    VoidCallback action, {
    Duration delay = const Duration(milliseconds: 500),
  }) {
    _debounceTimer?.cancel();
    _debounceTimer = Timer(delay, action);
  }

  @override
  void dispose() {
    // Cancels any pending debounce timer automatically.
    // The consuming class never needs to manage this manually.
    _debounceTimer?.cancel();
    super.dispose();
  }
}
// lib/mixins/loading_state_mixin.dart

import 'package:flutter/material.dart';

/// Manages loading, error, and idle states for async operations.
///
/// Requires: must be applied to a State object.
///
/// Provides:
/// - [isLoading]: true while an operation is running.
/// - [hasError]: true if the last operation failed.
/// - [error]: the error object from the last failure.
/// - [runWithLoading]: wraps any async operation with automatic state management.
/// - [clearError]: resets the error state.
mixin LoadingStateMixin on State {
  bool _isLoading = false;
  Object? _error;

  bool get isLoading => _isLoading;
  bool get hasError => _error != null;
  Object? get error => _error;

  /// Runs [operation], automatically setting loading state before it starts
  /// and clearing it when it finishes (whether successfully or not).
  /// Returns the result of [operation], or null if it threw an error.
  Future runWithLoading(Future Function() operation) async {
    if (_isLoading) return null;

    setState(() {
      _isLoading = true;
      _error = null;
    });

    try {
      final result = await operation();
      if (mounted) setState(() => _isLoading = false);
      return result;
    } catch (e) {
      if (mounted) {
        setState(() {
          _isLoading = false;
          _error = e;
        });
      }
      return null;
    }
  }

  /// Clears the current error state, returning the UI to idle.
  void clearError() {
    setState(() => _error = null);
  }
}

ڈیٹا ماڈلز اور جعلی خدمات

// lib/models/search_result.dart

class SearchResult {
  final String id;
  final String title;
  final String subtitle;
  final String category;

  const SearchResult({
    required this.id,
    required this.title,
    required this.subtitle,
    required this.category,
  });
}
// lib/services/search_service.dart

import '../models/search_result.dart';

class SearchService {
  static const _fakeResults = [
    SearchResult(id: '1', title: 'Flutter Basics', subtitle: 'Getting started with Flutter', category: 'Tutorial'),
    SearchResult(id: '2', title: 'Dart Mixins', subtitle: 'Deep dive into Dart mixin system', category: 'Article'),
    SearchResult(id: '3', title: 'State Management', subtitle: 'Bloc, Riverpod, and Provider compared', category: 'Guide'),
    SearchResult(id: '4', title: 'Flutter Animations', subtitle: 'Animation controllers and tickers', category: 'Tutorial'),
    SearchResult(id: '5', title: 'GraphQL Flutter', subtitle: 'Using graphql_flutter in production', category: 'Guide'),
    SearchResult(id: '6', title: 'Testing Flutter Apps', subtitle: 'Unit, widget, and integration tests', category: 'Article'),
  ];

  Future> search(String query) async {
    // Simulate a network delay
    await Future.delayed(const Duration(milliseconds: 600));

    if (query.trim().isEmpty) return [];

    return _fakeResults
        .where((r) =>
            r.title.toLowerCase().contains(query.toLowerCase()) ||
            r.subtitle.toLowerCase().contains(query.toLowerCase()))
        .toList();
  }
}

تلاش کی سکرین

// lib/screens/search_screen.dart

import 'package:flutter/material.dart';
import '../mixins/logger_mixin.dart';
import '../mixins/debounce_mixin.dart';
import '../mixins/loading_state_mixin.dart';
import '../models/search_result.dart';
import '../services/search_service.dart';

class SearchScreen extends StatefulWidget {
  const SearchScreen({super.key});

  @override
  State createState() => _SearchScreenState();
}

class _SearchScreenState extends State
    // AutomaticKeepAliveClientMixin: preserves this tab's state when the user
    // switches to another tab and then returns. The search query and results
    // stay intact without re-fetching.
    with
        AutomaticKeepAliveClientMixin,
        // LoggerMixin: provides log() and logError() throughout this State.
        // No `on State` constraint because it is a pure Dart mixin.
        LoggerMixin,
        // DebounceMixin: provides debounce() and auto-cancels the timer on dispose.
        DebounceMixin,
        // LoadingStateMixin: provides runWithLoading(), isLoading, hasError, error.
        LoadingStateMixin {

  // AutomaticKeepAliveClientMixin requires this getter.
  // Returning true keeps this widget alive when it scrolls off screen
  // or when the user navigates away in a TabView or PageView.
  @override
  bool get wantKeepAlive => true;

  final _searchController = TextEditingController();
  final _searchService = SearchService();
  List _results = [];
  String _lastQuery = '';

  @override
  void initState() {
    // The mixin linearization order matters here.
    // super.initState() calls through the chain:
    // LoadingStateMixin -> DebounceMixin -> AutomaticKeepAliveClientMixin -> State
    super.initState();
    log('SearchScreen initialized');
  }

  @override
  void dispose() {
    // DebounceMixin.dispose() is called via super.dispose() automatically.
    // We only need to dispose resources we explicitly own.
    _searchController.dispose();
    // super.dispose() chains through all mixins' dispose methods.
    super.dispose();
    log('SearchScreen disposed');
  }

  // Called every time the search text field changes.
  void _onSearchChanged(String query) {
    // DebounceMixin.debounce() delays the actual search call by 500ms.
    // If the user types another character within 500ms, the timer resets.
    // This prevents a network call on every single keystroke.
    debounce(() => _performSearch(query));
  }

  Future _performSearch(String query) async {
    if (query == _lastQuery) return; // Avoid redundant searches
    _lastQuery = query;

    log('Searching for: "$query"');

    if (query.trim().isEmpty) {
      setState(() => _results = []);
      return;
    }

    // LoadingStateMixin.runWithLoading() handles all the state transitions:
    // sets isLoading = true before the call,
    // sets isLoading = false when it completes,
    // captures any error into the error property if it throws.
    final results = await runWithLoading(
      () => _searchService.search(query),
    );

    if (results != null && mounted) {
      setState(() => _results = results);
      log('Search returned ({results.length} results for ")query"');
    }
  }

  @override
  Widget build(BuildContext context) {
    // AutomaticKeepAliveClientMixin REQUIRES super.build(context) to be called.
    // Without it, the keep-alive mechanism never activates.
    super.build(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Search'),
        bottom: PreferredSize(
          preferredSize: const Size.fromHeight(56),
          child: Padding(
            padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
            child: TextField(
              controller: _searchController,
              onChanged: _onSearchChanged,
              decoration: InputDecoration(
                hintText: 'Search articles, tutorials...',
                prefixIcon: const Icon(Icons.search),
                suffixIcon: _searchController.text.isNotEmpty
                    ? IconButton(
                        icon: const Icon(Icons.clear),
                        onPressed: () {
                          _searchController.clear();
                          _onSearchChanged('');
                        },
                      )
                    : null,
                filled: true,
                fillColor: Theme.of(context).colorScheme.surfaceVariant,
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(12),
                  borderSide: BorderSide.none,
                ),
              ),
            ),
          ),
        ),
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    // LoadingStateMixin.isLoading and hasError are available here
    // because of the mixin composition.

    if (isLoading) {
      return const Center(child: CircularProgressIndicator());
    }

    if (hasError) {
      return Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Icon(Icons.error_outline, size: 48, color: Colors.red),
            const SizedBox(height: 12),
            Text(
              error?.toString() ?? 'An error occurred',
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                clearError(); // LoadingStateMixin.clearError()
                _performSearch(_lastQuery);
              },
              child: const Text('Retry'),
            ),
          ],
        ),
      );
    }

    if (_searchController.text.isEmpty) {
      return const Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Icons.search, size: 64, color: Colors.grey),
            SizedBox(height: 16),
            Text(
              'Start typing to search',
              style: TextStyle(color: Colors.grey, fontSize: 16),
            ),
          ],
        ),
      );
    }

    if (_results.isEmpty) {
      return Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Icon(Icons.search_off, size: 64, color: Colors.grey),
            const SizedBox(height: 16),
            Text(
              'No results for "${_searchController.text}"',
              style: const TextStyle(color: Colors.grey, fontSize: 16),
            ),
          ],
        ),
      );
    }

    return ListView.separated(
      padding: const EdgeInsets.all(16),
      itemCount: _results.length,
      separatorBuilder: (_, __) => const SizedBox(height: 8),
      itemBuilder: (context, index) {
        final result = _results[index];
        return SearchResultCard(result: result);
      },
    );
  }
}

class SearchResultCard extends StatelessWidget {
  final SearchResult result;

  const SearchResultCard({super.key, required this.result});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: ListTile(
        leading: CircleAvatar(
          backgroundColor: _categoryColor(result.category),
          child: Text(
            result.category[0],
            style: const TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        title: Text(
          result.title,
          style: const TextStyle(fontWeight: FontWeight.w600),
        ),
        subtitle: Text(result.subtitle),
        trailing: Chip(
          label: Text(
            result.category,
            style: const TextStyle(fontSize: 11),
          ),
          padding: EdgeInsets.zero,
          visualDensity: VisualDensity.compact,
        ),
      ),
    );
  }

  Color _categoryColor(String category) {
    switch (category) {
      case 'Tutorial':
        return Colors.blue;
      case 'Article':
        return Colors.green;
      case 'Guide':
        return Colors.orange;
      default:
        return Colors.purple;
    }
  }
}

یہ SearchScreen ایک سے زیادہ مکسنس کو یکجا کرنے کا طریقہ دکھاتا ہے۔ State اسباق جو صاف طور پر خدشات کو الگ کرتے ہیں۔ AutomaticKeepAliveClientMixin ٹیبز کو سوئچ کرتے وقت اسکرین کی حالت کو برقرار رکھتا ہے۔ LoggerMixin لاگنگ کو سنبھالنا، DebounceMixin ان پٹ پروسیسنگ میں تاخیر کرکے ضرورت سے زیادہ سرچ کالز کو روکتا ہے۔ LoadingStateMixin لوڈنگ اور خرابی کی حالتوں کا انتظام کرتا ہے۔ یہ آپ کو اپنے UI اور منطق کو منظم رکھنے کی اجازت دیتا ہے جب کہ سوالات کو ڈیباؤنس کرتے ہوئے اور بلٹ ان لوڈ/ایرر ہینڈلنگ کا استعمال کرتے ہوئے تلاشیں چلانے اور نتائج کو مؤثر طریقے سے اپ ڈیٹ کرتے ہیں، جبکہ اسکرین صارف کے ان پٹ پر رد عمل ظاہر کرتی ہے۔

داخلہ پوائنٹ

// lib/main.dart

import 'package:flutter/material.dart';
import 'screens/search_screen.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Mixins Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        useMaterial3: true,
      ),
      home: DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            bottom: const TabBar(
              tabs: [
                Tab(icon: Icon(Icons.search), text: 'Search'),
                Tab(icon: Icon(Icons.home), text: 'Home'),
              ],
            ),
          ),
          body: const TabBarView(
            children: [
              SearchScreen(), // Uses four mixins
              Center(child: Text('Home Tab')),
            ],
          ),
        ),
      ),
    );
  }
}

یہ مکمل، قابل عمل مثال تمام کلیدی مرکب تصورات کو سیاق و سباق میں رکھتی ہے۔

کہ _SearchScreenState بیک وقت چار مکسنس استعمال کریں:

  1. AutomaticKeepAliveClientMixin ٹیب رہنے کے لیے

  2. LoggerMixin سٹرکچرڈ لاگنگ کے لیے جس کے لیے سیٹ اپ کی ضرورت نہیں ہے۔

  3. DebounceMixin ڈسپوزل پر خودکار ٹائمر کلین اپ کے ساتھ خودکار بازیافت ڈیباؤنس کے لیے؛

  4. اور LoadingStateMixin کلین غیر مطابقت پذیر کام ریاستی انتظام کے لیے۔

مکس لائن لائنرائزیشن آرڈر جان بوجھ کر ہے اور اس پر تبصرہ کیا گیا ہے۔ کہ super سلسلہ دونوں میں شاندار ہے۔ initState اور dispose. ہر مکس کی بالکل ایک ذمہ داری ہوتی ہے۔ استعمال State کلاس صرف اپنی منطق پر فوکس کرتی ہے، جو UI کو سرچ سروس سے منسلک کرتی ہے۔

نتیجہ

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

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

بصیرت جو مکسن کلک کرتی ہے وہ "is-a” اور "can-do” کے درمیان فرق کو سمجھ رہی ہے۔ وراثت کا مطلب شناخت کا نمونہ ہے۔ Dog چاندی Animal. مکسین ماڈلنگ کی فعالیت کے لیے ہیں۔ آپ اسکرین پر تجزیات کو ٹریک کر سکتے ہیں، ریپوزٹریوں میں لاگ ان کر سکتے ہیں، اور فارم کی توثیق کر سکتے ہیں۔ ایک بار جب آپ اس امتیاز کو اندرونی بنا لیں گے، تو آپ قدرتی طور پر اپنے موجودہ کوڈ میں مکس ان مواقع کی نشاندہی کریں گے۔

فلٹر کا اپنا فریم ورک مکسین ڈیزائن میں ایک ماسٹر کلاس ہے۔ جب بھی میں ٹائپ کرتا ہوں۔ with SingleTickerProviderStateMixinتم ہو Tickerکا پورا لائف سائیکل پوشیدہ ہے اور صرف ایک فنکشن کے ساتھ صحیح قسم کی کلاسوں پر فعال ہے (vsync) جب کوئی ویجیٹ حذف ہوجاتا ہے، تو یہ مکمل طور پر غائب ہوجاتا ہے۔ یہ وہ آئیڈیل ہے جس کی خواہش ہے: زیادہ سے زیادہ فعالیت، سطح کا کم از کم رقبہ، اور صفر میموری کا لیک۔

لکیریائزیشن ماڈل ڈارٹ کے مکسن سسٹم کو بھروسہ فراہم کرتے ہیں۔ جہاں ایک سے زیادہ وراثت ابہام پیدا کرتی ہے، لکیریائزیشن ایک تعییناتی زنجیر بناتی ہے جہاں تمام مکسنز کو ایک پیش قیاسی ترتیب میں عمل میں لایا جاتا ہے اور تمام مکسنز کو عمل میں لایا جاتا ہے۔ super کال اگلے لنک کے ساتھ جاری ہے۔ اس سلسلہ کو سمجھیں اور ہمیشہ اس کا احترام کریں۔ super لائف سائیکل اوور رائڈ کالز مکسنس کے محفوظ استعمال کے لیے واحد اہم ترین مکینیکل ایریا ہیں۔

اپنے مکسنس کو اچھی طرح سے لکھنے کے لیے وہی نظم و ضبط درکار ہوتا ہے جیسا کہ اچھے فنکشنز لکھنا۔ اس کا مطلب ہے کہ آپ کو ایک ذمہ داری، ایک واضح نام، ایک تحریری معاہدہ، اور آزاد جانچ کے امکان کی ضرورت ہے۔

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

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

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

حوالہ جات

ڈارٹ زبان کی دستاویزات

فلٹر فریم ورک مکسین

سیکھنے کے مواد

  • موثر ڈارٹس: ڈیزائن: ڈارٹ API ڈیزائن کے لیے گوگل کی آفیشل اسٹائل گائیڈ، بشمول کلاسز، مکسینز، اور ایکسٹینشن کے طریقے استعمال کرنے کے بارے میں رہنمائی۔ https://dart.dev/active-dart/design

  • ہفتہ کا فلٹر ویجیٹ: مکسین پر مبنی ویجیٹ: فلٹر کی آفیشل یوٹیوب سیریز میں کئی اقساط شامل ہیں جس میں بتایا گیا ہے کہ فلٹر کے ویجیٹ سسٹم کو کس طرح مکس کرتا ہے۔ https://www.youtube.com/@flutterdev

  • ڈارٹ اسپیکس: مکسنس: یہ ان قارئین کے لیے مکسنس کے لیے سرکاری زبان کی تفصیلات کا سیکشن ہے جو لکیریائزیشن اور مکسائن ایپلی کیشن کے صحیح اصولوں کو سمجھنا چاہتے ہیں۔ https://dart.dev/guides/language/spec

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