مفاهیم مقدماتی فلاتر (Flutter) — به زبان ساده

مقدمه:بسیاری از افراد وقتی با مشکلی مواجه می‌شوند، نمی‌دانند که باید اشکال کار را در کجا جستجو کنند: Dart ،Flutter ،Window ،Screen ،Route یا Widget؟از سوی دیگر مطالعه مستندات Dart ،Flutter و همه ویجت‌های آن نیز ایده چندان مناسبی محسوب نمی‌شود چون به زمان بسیار زیادی نیاز دارد. به دلیل همه مطالبی که گفته شد، تصمیم گرفتیم در این مقاله به معرفی مفاهیم ضروری برای درک این فریمورک و توانایی نوشتن اپلیکیشن‌های ساده بپردازیم. با این که بسیاری از راهنماها در این حوزه به خوبی نوشته شده‌اند و سرراست هستند؛ اما مشکل اینجا است که در آن‌ها تصور شده، شما با برخی مسائل مقدماتی آشنا هستید و این مسائل کوچک در مقالاتی که قرار است دانش مقدماتی به شما بدهند لحاظ نشده‌اند.در این سری مطالب آموزشی با عنوان راهنمای جامع Flutter این مشکل حل شده است. ما از صفر شروع می‌کنیم و اپلیکیشن‌هایی ایجاد می‌کنیم که در آن همه مراحل مورد نیاز به خوبی توضیح داده شده است. در طی این سری از مطالب آموزشی از همه ویجت‌های مقدماتی استفاده می‌کنیم؛ یک اینترفیس منحصر به فرد طراحی می‌کنیم، با ماژول‌های نیتیو (Native) تعامل خواهیم داشت و اپلیکیشن خود را برای هر دو پلتفرم اندروید و iOS بیلد (build) می‌کنیم.این سری مطالب از چشم‌انداز یک توسعه‌دهنده وب نوشته شده‌اند. اغلب شما احتمالاً با مجموعه نرم‌افزارهای مورد نیاز برای توسعه وب آشنا هستید. در هر صورت استفاده از مثال‌هایی از حوزه توسعه وب بسیار بهتر از مثال‌هایی است که از حوزه‌های نامرتبط آورده می‌شوند.معرفی پلتفرم Flutter:برخلاف بسیاری از پلتفرم‌های رایج دیگر برای توسعه موبایل، Flutter به هیچ وجه از جاوا اسکریپت استفاده نمی‌کند. Dart یک زبان برنامه‌نویسی است. این زبان به صورت کد باینری کامپایل می‌شود و به همین دلیل است که عملکرد بومی آن قابل قیاس با زبان‌هایی مانند Objective-C، Swift، Java یا Kotlin است. از آنجا که کامپوننت‌ها در خود Flutter پیاده‌سازی شده‌اند، هیچ لایه ارتباطی بین view و کد ما وجود نخواهد داشت. به همین جهت بازی‌ها در Flutter بهترین سرعت خود را در زمینه استفاده از گرافیک در گوشی‌های هوشمند کسب می‌کنند. بنابراین دکمه‌ها، متون، عناصر رسانه‌ای، و پس‌زمینه‌ها همگی در موتور گرافیکی Flutter ترسیم می‌شوند. همچنین باید توجه داشته باشید که حجم کلی اپلیکیشن Flutter «Hello World» کاملاً کوچک است و در iOS تقریباً برابر با 2.5 مگابایت و در اندروید به میزان 4 مگابایت است.علاوه بر همه موارد فوق، این فریمورک دارای امکان یکپارچه شده «بارگذاری مجدد خودکار» (Hot-reload) است که در عرصه وب مدت‌ها است جا افتاده است؛ اما در پلتفرم‌های native همچنان مفقود است. این امکان باعث می‌شود که فریمورک Flutter به طور خودکار درخت ویجت‌ها را بازسازی کند و بدین ترتیب می‌توان تأثیر تغییرات ایجاد شده را بسیار به سرعت مشاهده کرد.اگر بخواهیم فریمورک Flutter را به طور خلاصه برای یک توسعه‌دهنده اندروید توضیح دهیم، باید اشاره کنیم که به طور مثال یک پروژه اندروید که در زبان جاوا نوشته شده و تعداد 179 فایل و 12176 خط کد دارد، زمانی که در Dart نوشته شود، تعداد فایل‌های آن به 31 و تعداد خطوط کد به 1735 کاهش می‌یابد.ویجت‌های بی‌حالت (Stateless)اینک می‌دانیم که استفاده از ویجت‌های Flutter تا چه حد آسان است. مرحله منطقی بعدی این است که ویجت‌های خود را بسازیم. قبلاً اشاره کردیم که دو نوع ویجت وجود دارند. البته در عمل نوع ویجت‌ها بیشتر است؛ اما نمی‌خواهیم فعلاً وارد پیچیدگی آن‌ها شویم. به طور کلی دو نوع ویجت باحالت (stateful) و بی‌حالت (stateless) وجود دارند.منظور از بی‌حالت این است که این ویجت‌ها هیچ گونه حالتی را نمی‌پذیرند. ویجت‌ها نوعی کلاس Dart هستند که می‌توان با استفاده از مشخصات آن‌ها را اعلان کرد، اما تغییر دادن این مشخصات در ویجت بی‌حالت بر آنچه قبلاً رندر شده است تأثیر نمی‌گذارد. به‌روزرسانی مشخصات یک ویجت باحالت موجب تحریک قلاب‌های چرخه عمر آن می‌شود و محتوای آن با استفاده از حالت جدید به‌روزرسانی می‌شود. در ابتدا با توضیح ویجت‌های بی‌حالت آغاز می‌کنیم چون ساده‌تر به نظر می‌رسند.برای ایجاد یک ویجت بی‌حالت مراحل زیر باید طی شوند:انتخاب یک نام مناسب برای کلاس جدیدبسط (extend) دادن کلاس از StatelessWidgetپیاده‌سازی متد ()build که یک آرگومان از نوع BuildContext می‌گیرد و نتیجه‌ای از نوع Widget باز می‌گرداند.import 'package:flutter/widgets.dart'; main() => runApp( Directionality( textDirection: TextDirection.ltr, child: Center( child: MyStatelessWidget() ), ), ); class MyStatelessWidget extends StatelessWidget { // @override annotation is needed for optimization, by using it // we say that we don't need the same method from the parent class // so the compiler can drop it @override Widget build(BuildContext context) { // I'll describe [context] later return Text('Hello!'); } }نمونه‌ای از ویجت با یک آرگومان:// …class MyStatelessWidget extends StatelessWidget { // All properties of the Stateless widget must be declared with final or const keyword final String name; // usual class property MediumSimple(this.name); // usual class constructor @override Widget build(BuildContext context) { // it is yet to early to describe [context] return Text('Hello, $name!'); }}بارگذاری مجدد خودکار (Hot Reload):دقت کنید که ما محتوای اپلیکیشن خود را به یک ویجت مجزا انتقال دادیم. اپلیکیشن هر بار که تغییرات را ذخیره کنیم مجدداً بارگذاری می‌شود. این عمل Hot Reload نام دارد. همچنین باید درک کنید که وقتی مشغول کار در حالت توسعه با وضعیت فعال شده Hot Reload هستیم، اپلیکیشن بسیار کندتر از وضعیت انتشار خود خواهد بود.GestureDetector:ویجت GestureDetector که به مدیریت عمل tap کردن می‌پردازد. https://virgool.io/d/sazldnwjyhok/%D9%88%DB%8C%D8%AC%D8%AAGestureDetector%DA%A9%D9%87%D8%A8%D9%87%D9%85%D8%AF%DB%8C%D8%B1%DB%8C%D8%AA%D8%B9%D9%85%D9%84tap%DA%A9%D8%B1%D8%AF%D9%86%D9%85%DB%8C%E2%80%8C%D9%BE%D8%B1%D8%AF%D8%A7%D8%B2%D8%AF. در مثال زیر یک دکمه آبی رنگ در مرکز صفحه ایجاد می‌کنیم و زمانی که دکمه فشار داده شود، متنی روی ترمینال نمایش می‌یابد.import 'package:flutter/widgets.dart'; main() => runApp( Directionality( textDirection: TextDirection.ltr, child: Container( color: Color(0xFFFFFFFF), child: App(), ), ), ); class App extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: GestureDetector( // just a normal widget onTap: () { // one of the [GestureDetector] properties // This function will be called when child widget is pressed print('You pressed me'); }, child: Container( // the [Container] will represent our button decoration: BoxDecoration( // this is how you style the [Container] shape: BoxShape.circle, // change its shape from rectangular to circular color: Color(0xFF17A2B8), // and paint it in blue ), width: 80.0, height: 80.0, ), ), ); } }دکمه را فشار دهید تا پیام در ترمینال نمایش یابد. اگر آن را مجدداً فشار دهید، متن دوباره ظاهر خواهد شد.ویجت‌های باحالت:ویجت‌های باحالت نیز مفهوم ساده‌ای هستند. شاید حتی ساده‌تر از ویجت‌های بی‌حالت باشند. با این وجود یک مشکل وجود دارد. این ویجت‌ها به وسیله خودشان ایجاد نمی‌شوند. آن‌ها برای ذخیره‌سازی حالت ویجت به یک کلاس دیگر نیاز دارند. به علاوه بخش بصری ویجت به حالت خودش تبدیل می‌شود. در ادامه مثالی از کلاس StatefulWidget را می‌بینید:// …class Counter extends StatefulWidget { // The state is stored not in the widget, but in the specific class // that is created by createState() @override State<Counter> createState() => _CounterState(); // The result of the function is an object, that must be // of the type State<Counter> (where Counter is the name of our widget)}در کد فوق ما یک ویجت خالی ایجاد کرده‌ایم که تنها یک متد را پیاده‌سازی می‌کند و شامل هیچ حالت یا بازنمایی UI نیست. Flutter با اجبار کردن یک چنین جداسازی به دنبال بهینه‌سازی اپلیکیشن‌ها بوده است.شیء حالت نیز پیچیده نیست. در واقع مانند StatelessWidget است. تفاوت اصلی در کلاس والد آن است.// … class _CounterState extends State<Counter> { // Finally, we can declare dynamic variables inside of our classes, // to store the state of our widgets // In this case, we'll store the number int counter = 0; // The rest is super simple, we just implement the familiar to us build() method, // in the same way as we did it for our [StatelessWidget] @override Widget build(BuildContext context) { // Almost nothing has changed since the last example. // I've added comments to highlight the difference return Center( child: GestureDetector( onTap: () { // Once the button is tapped we increase the value of [counter] variable setState(() { // Using setState() is required to trigger lifecycle hooks // so the widget will know that it should be updated ++counter; }); }, child: Container( decoration: BoxDecoration( shape: BoxShape.circle, color: Color(0xFF17A2B8), ), width: 80.0, child: Center( child: Text( // here we print the value of the [counter] '$counter', // to see how it changes style: TextStyle(fontSize: 30.0), ), ), ), ), ); } }اپلیکیشن شمارنده با استفاده از ویجت‌های مقدماتی Flutter ساخته شده است. ما نام کلاس حالت خود را با یک زیرخط تعریف کرده‌ایم. در زبان Dart همه نام‌هایی که با یک زیرخط آغاز می‌شوند خصوصی هستند و برخلاف پایتون یا جاوا اسکریپت واقعاً از خارج از کتابخانه غیر قابل دسترسی هستند. معمولاً ما نیازی به افشای کلاس‌های حالت در خارج از کتابخانه نداریم و بنابراین بهتر است که آن‌ها را به صورت خصوصی تعریف کنیم. می‌بینید که چگونه به سادگی یک چنین اپلیکیشن عالی ایجاد کرده‌ایم.پیش از آغاز این بخش از سری مطالب آموزش جامع Flutter نگاهی به برخی ویجت‌های جالب دیگر می‌اندازیم. این بار کد بیشتری را نوشته‌ایم و قصد نداریم همه خطوط را توضیح دهیم. شما احتمالاً با توجه به توضیحاتی که در این راهنما خواندید، می‌توانید اغلب بخش‌های کد زیر را متوجه شوید:import 'package:flutter/widgets.dart'; main() => runApp(App()); class App extends StatelessWidget { @override Widget build(BuildContext context) { return Directionality( textDirection: TextDirection.ltr, child: Container( padding: EdgeInsets.symmetric( vertical: 60.0, horizontal: 80.0, ), color: Color(0xFFFFFFFF), child: Content(), ), ); } } class Content extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: [ Counter('Manchester United'), Counter('Juventus'), ], ); } } class Counter extends StatefulWidget { final String _name; Counter(this._name); @override State<Counter> createState() => _CounterState(); } class _CounterState extends State<Counter> { int count = 0; @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.only(bottom: 10.0), padding: EdgeInsets.all(4.0), decoration: BoxDecoration( border: Border.all(color: Color(0xFFFD6A02)), borderRadius: BorderRadius.circular(4.0), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // [widget] is the property of the State class that stores // the instance of the [StatefulWidget] ([Counter] in our case) _CounterLabel(widget._name), _CounterButton( count, onPressed: () { setState(() { ++count; }); }, ), ], ), ); } } class _CounterLabel extends StatelessWidget { static const textStyle = TextStyle( color: Color(0xFF000000), fontSize: 26.0, ); final String _label; _CounterLabel(this._label); @override Widget build(BuildContext context) { return Text( _label, style: _CounterLabel.textStyle, ); } } class _CounterButton extends StatelessWidget { final count; final onPressed; _CounterButton(this.count, {@required this.onPressed}); @override Widget build(BuildContext context) { return GestureDetector( onTap: onPressed, child: Container( padding: EdgeInsets.symmetric(horizontal: 6.0), decoration: BoxDecoration( color: Color(0xFFFD6A02), borderRadius: BorderRadius.circular(4.0), ), child: Center( child: Text( '$count', style: TextStyle(fontSize: 20.0), ), ), ), ); }بدین ترتیب ما از دو ویجت ()Column و ()Row استفاده کرده‌ایم. حدس زدن هدف این دو ویجت کار چندان دشواری نیست. در مقاله بعدی از این سری مطالب آموزش جامع Flutter این ویجت‌ها را دقیق‌تر بررسی خواهیم کرد. همچنین با روش مونتاژ چندین ویجت و ایجاد یک اپلیکیشن زیبا با استفاده از کتابخانه Material Flutter آشنا می‌شویم.ممنونم از همراهی شما