معماری اپلیکیشن فلاتر — راهنمای مقدماتی
فلاتر آزادی زیادی در زمینه کارهایی که میتوان انجام داد فراهم ساخته است. البته برخی اوقات مانند زمانی که تازه شروع به کار کردهاید، این آزادی میتواند یک عیب باشد. این نکته به طور خاص در مورد مدیریت حالت (State) و معماری اپلیکیشن صدق میکند. در این مقاله با مفاهیم معماری اپلیکیشن فلاتر آشنا خواهیم شد.
برخی توسعهدهندگان جامعه فلاتر کارهای خوبی در زمینه طراحی مدل سازماندهی اپلیکیشنهای فلاتر انجام دادهاند. اما درک این مدلها برای افراد مبتدی کمی دشوار است. در این راهنما کوشش میکنیم درکی سطح بالا از شیوه معماری اپلیکیشن فلاتر ارائه کرده و نوعی راهنمای عملی در مورد استفاده از پکیج Provider Architecture در اختیار مخاطبان عزیز قرار دهیم.
مقدمهای بر Provider
FilledStacks از یک معماری به سبک MVVM بهره میگیرد.
در این معماری «نما» (View) معمولاً یک لیآوت ویجت برای یک صفحه از اپلیکیشن است. این نما شامل هیچ منطق یا حالتی نیست و در «مدل نما» (View Model) جای گرفته است که از هیچ کدام از جزییات نما آگاهی ندارد. اغلب اپلیکیشن ها نیاز دارند به دادهها دسترسی داشته باشند و آنها را ذخیره سازند و این کار توسط سرویسها انجام مییابد که در عمل همان کلاسهای دارت (Dart) محسوب میشوند و اطلاعی از جزییات امور ندارند. به همین جهت مدلهای نما لازم نیست در مورد شیوه انجام کار دغدغهای داشته باشند.
ما برای هر صفحه روی اپلیکیشن یک مدل نما ایجاد میکنیم. سرویسها به صورت سراسری در دسترس ما هستند و از این رو بدون تغییر باقی میمانند. یک اپلیکیشن چندصفحهای ساختاری مانند زیر خواهد داشت:
به بیان عملی فایلها میتوانند به صورت زیر سازماندهی شوند:
البته استفاده از هر سازماندهی دیگری نیز آزاد است. FilledStacks معمولاً مدلهای نما را در پوشه core قرار میدهد. شاید شما ترجیح بدهید آنها را به نماهایی که از آنها استفاده میکنند نزدیکتر قرار دهید، اما به نظر میرسد آن سازماندهی که FilledStacks استفاده میکند، بهتر است.
مثال
اکنون یک مثال ساده را بررسی میکنیم تا معماری فوق را پیادهسازی کنیم. در ادامه با شیوه ایجاد یک نما و مدل نمای مرتبط با آن آشنا میشویم. پیشنهاد میکنیم روی این مثال وقت بگذارید و به جای مطالعه صرف، آن را عملاً پیادهسازی کنید. بدین ترتیب درک بهتری از مفاهیم به دست میآورید.
شروع
یک پروژه جدید آغاز کنید. هر نامی که دوست دارید برای آن بگذارید. یک اپلیکیشن شمارنده پیشفرض به دست میآورید که در ادامه آن را با سبک معماری MVVM با استفاده از Provider Architecture پکیج ویرایش خواهیم کرد.
پکیج provider_architecture را به فایل pubspec.yaml اضافه کنید:
dependencies: provider_architecture: ^1.0.5
نکته: ما عملاً نیازی نداریم که از پکیج Provider Architecture استفاده کنیم تا این الگوی معماری را پیادهسازی نماییم. اما این کار مزیتهای دیگری از قبیل حذف برخی کدهای آماده و همچنین پنهانسازی برخی از پیچیدگیهای پکیج Provider را دارد که به صورت درونی مورد استفاده قرار میگیرد.
در این مثال قصد نداریم ساختار پوشه کاملی که در بخش قبل دیدیم را ایجاد کنیم، اما اگر شما تمایل داشته باشید، میتوانید چنین کنید.
مدل نما
در پوشه /lib یک فایل جدید به نام counter_viewmodel.dart ایجاد کنید. سپس کد زیر را در آن قرار دهید
1import 'package:flutter/foundation.dart';
2
3class CounterViewModel extends ChangeNotifier { // <-- extends ChangeNotifier
4 int _counter = 0;
5
6 int get counter => _counter;
7
8 void increment() {
9 _counter++;
10 notifyListeners(); // <-- notify listeners
11 }
12}
توجه داشته باشید که این کلاس ChangeNotifier را بسط میدهد که متد ()notifyListeners را در اختیار ما قرار میدهد. این نما یک شنونده خواهد بود و از این رو این متد آن چیزی است که پکیج Provider Architecture برای ساخت مجدد UI در موارد بروز تغییر مورد استفاده قرار میدهد. ChangeNotifier بخشی از foundation در فلاتر محسوب میشود و از این رو عملاً هیچ وابستگی به پکیجهای provider_architecture یا provider در مدل نما ندارد.
نما
یک فایل جدید به نام counter_screen.dart در پوشه /lib ایجاد کنید. این همان لیآوت ویجت UI برای صفحه counter است. کد زیر را در آن وارد کنید:
1import 'package:flutter/material.dart';
2import 'package:flutter_architecture_example/counter_viewmodel.dart';
3import 'package:provider_architecture/provider_architecture.dart';
4
5// Since the state was moved to the view model, this is now a StatelessWidget.
6class CounterScreen extends StatelessWidget {
7 @override
8 Widget build(BuildContext context) {
9 // ViewModelProvider is what provides the view model to the widget tree.
10 return ViewModelProvider<CounterViewModel>.withConsumer(
11 viewModel: CounterViewModel(),
12 builder: (context, model, child) => Scaffold(
13 appBar: AppBar(
14 title: Text('Flutter Demo Home Page'),
15 ),
16 body: Center(
17 child: Column(
18 mainAxisAlignment: MainAxisAlignment.center,
19 children: <Widget>[
20 Text(
21 'You have pushed the button this many times:',
22 ),
23 Text(
24 '${model.counter}', // <-- view model
25 style: Theme.of(context).textTheme.display1,
26 ),
27 ],
28 ),
29 ),
30 floatingActionButton: FloatingActionButton(
31 onPressed: () {
32 model.increment(); // <-- view model
33 },
34 tooltip: 'Increment',
35 child: Icon(Icons.add),
36 ),
37 ),
38 );
39 }
40}
ViewModelProvider را در ابتدای متد build() میبینید. این همان چیزی است که CounterViewModel در اختیار درخت ویجت قرار داده است. از آنجا که «حالت» (State) در مدل نما قرار دارد، نیازی به استفاده از یک «ویجت باحالت» (StatefulWidget) وجود ندارد. از این رو میبینید که CounterScreen یک StatelessWidget است. این صفحه یک مدل نما میگیرد. زمانی که دکمه فشرده شود این صفحه متد ()increment را روی مدل نما فراخوانی خواهد کرد که به نوبه خود موجب تحریک یک بازسازی با مقدار counter جدید میشود.
همچنین باید main.dart را پاکسازی کنیم. کد آن را به صورت زیر جایگزین کنید:
1import 'package:flutter/material.dart';
2import 'counter_view.dart';
3
4void main() => runApp(MyApp());
5
6class MyApp extends StatelessWidget {
7 @override
8 Widget build(BuildContext context) {
9 return MaterialApp(
10 title: 'Flutter Demo',
11 theme: ThemeData(
12 primarySwatch: Colors.blue,
13 ),
14 home: CounterScreen(),
15 );
16 }
17}
اکنون اگر اپلیکیشن را اجرا کنید، باید دقیقاً همانند اپلیکیشن پیشفرض شمارنده رفتار کند.
مزیت استفاده از پکیج Provider Architecture
تا به اینجا هر کاری انجام دادیم، با استفاده از پکیج Provider قدیمی و با بهرهگیری از یک ChangeNotifierProvider و یک Consumer نیز قابل انجام بود. اما یکی از نیازهای رایج، واکشی برخی دادههای اولیه از شبکه و یا یک پایگاه است. مدل نمای شما میتواند یک متد عرضه کند که این وظایف اولیه را انجام میدهد.
کد موجود در فایل counter_viewmodel.dart را با کد زیر عوض کنید:
1import 'package:flutter/foundation.dart';
2
3class CounterViewModel extends ChangeNotifier {
4 int _counter = 0;
5 int get counter => _counter;
6
7 WebApi _webApi = serviceLocator<WebApi>(); // <-- service
8
9 Future loadData() async { // <-- load initial data
10 _counter = await _webApi.fetchValue();
11 notifyListeners();
12 }
13
14 void increment() {
15 _counter++;
16 notifyListeners();
17 }
18}
19
20// Fake service locator. Use GetIt in a real app.
21// Or inject the service in the view model constructor.
22WebApi serviceLocator<T>() {
23 return WebApi();
24}
25
26// Fake web api
27class WebApi {
28 Future<int> fetchValue() => Future.delayed(Duration(seconds: 2), () => 11);
29}
چنان که میبینید، اکنون متدی برای واکشی برخی دادههای اولیه از یک سرویس web API وجود دارد. در این نوشته قصد نداریم در مورد روش ایجاد یک سرویس صحبت کنیم، از این رو صرفاً یک کد ساختگی در انتها قرار میدهیم.
نکته مهم در مورد پکیج Provider Architecture این است که ViewModelProvider یک callback به نام onModelReady دارد که به ما امکان میدهد تا برخی کدهای اولیه را در زمانی که مدل نما آماده شده است اجرا کنیم.
در فایل counter_screen.dart آرگومان زیر را به ViewModelProvider اضافه کنید:
onModelReady: (model) => model.loadData(),
اینک زمانی که اپلیکیشن را اجرا کنید، پس از این که ویژگی دوثانیهای تکمیل شد به صورت خودکار بهروزرسانی میشود.
جمعبندی
در این بخش راهنمایی برای آشنایی با شیوه راهاندازی سریع آن در پروژهها ارائه میکنیم.
وابستگی
وابستگی provider_architecture را به فایل pubspect.yaml اضافه کنید:
dependencies: provider_architecture: 1.0.5
مدل نما
فایل جدیدی به نام my_screen_viewmodel.dart ایجاد کرده و یک کلاس دارت به آن اضافه کنید که ChangeNotifier را بسط دهد:
1import 'package:flutter/foundation.dart';
2class MyScreenViewModel extends ChangeNotifier {
3 int _someValue = 0;
4 int get someValue => _someValue;
5 Future loadData() async {
6 // do initialization...
7 notifyListeners();
8 }
9 void doSomething() {
10 // do something...
11 notifyListeners();
12 }
13}
نما
فایل جدیدی به نام my_screen.dart ایجاد کرده و لیآوت ویجت نرمال خود را برای آن صفحه بسازید.
یک ViewModelProvider به ابتدای درخت ویجت MyScreen خود اضافه کنید. آسانترین روش برای انجام این کار استفاده از یک میانبر برای قرار دادن ویجت فوقانی درون یک ویجت جدید است. با این حال، به جای این که یک child داشته باشید از => برای بازگشت دادن ویجت فوقانی از پارامتر builder استفاده کنید.
1import 'package:provider_architecture/provider_architecture.dart';
2class MyScreen extends StatelessWidget {
3 @override
4 Widget build(BuildContext context) {
5 return ViewModelProvider<MyScreenViewModel>.withConsumer(
6 viewModel: MyScreenViewModel(),
7 onModelReady: (model) => model.loadData(),
8 builder: (context, model, child) => MyTopWidget(
9
10 // your widget tree
11 ),
12 );
13 }
14}
سپس درون درخت ویجت میتوانید به مدل نما به صورت زیر دسترسی داشته باشید:
- model.someValue
- model.doSomething()
سخن پایانی
در این مقاله با معماری مقدماتی یک اپلیکیشن فلاتر آشنا شدیم. در زمانی که از پکیج Provider Architecture استفاده میکنید، برخی نیازهای دیگر هم وجود دارند که با آنها مواجه خواهید شد. برای کسب اطلاعات بیشتر در این خصوص پیشنهاد میکنیم از مستدات (+) آن بهره بگیرید. با این حال توضیحاتی که در این راهنما ارائه شدهاند برای شروع کار کافی هستند.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای پروژهمحور برنامهنویسی اندروید
- مجموعه آموزشهای برنامهنویسی
- مفاهیم مقدماتی فلاتر (Flutter) — به زبان ساده
- آموزش گوگل فلاتر (Flutter ): ساخت اپلیکیشن دستورهای آشپزی
- گوگل فلاتر (Flutter) از صفر تا صد — ساخت اپلیکیشن به کمک ویجت
==
سلام. سوالم اینه که بهترین معماری برای فلاتر چه معماری ای هست؟
bloc
provider
MVC
یا چی؟
با سلام؛
از همراهی شما با مجله فرادرس سپاسگزاریم. نکتهای که باید به آن توجه داشت این است که هر یک از معماریهای موجود برای فلاتر، مزایا و معایب خاص خود را دارند. کاربر، بسته به نیاز، ویژگیهای مد نظر خود و پروژهای که در دست اجرا دارد، باید معماری مورد نظر خود را برگزیند و نمیتواند یک معماری را به عنوان بهترین معماری برگزید.
پیروز، شاد و تندرست باشید.
مرسی از میثم خان لطفی عزیز در همه زمینه ها مقاله های فوق العاده شما خیلی بهم کمک کردن
فقط یه سوال دارم این معماری در پروژه های بزرگ مشگل ساز نیست؟
میشه بجای الگو های پیچیده مثل bloc وredux استفاده کرد.؟
سلام و وقت بخیر دوست عزیز؛
از ابراز لطف شما متشکرم. روشهای مدیریت حالت در فلاتر زیاد هستند و هر کدام برای موقعیتهای مشخصی مناسب هستند. به طور کلی پکیج پرُوایدر برای اپلیکیشنهای ساده مناسب است و برای موارد پیچیدهتر بهتر است از BLoC و Redux استفاده شود.
از توجه شما سپاسگزارم.