راهنمای اولیه معماری اپلیکیشن فلاتر
فلاتر آزادی عمل بسیار زیادی را در اختیار شما قرار میدهد اما این آزادی همیشه خوب نیست و برخی افرادی که در ابتدای پروسه یادگیری هستند، ممکن است مشکلاتی به دنبال داشته باشد. این موضوع برای معماری اپلیکیشن فلاتر و مدیریت حالت (State) صدق میکند.
در سایتهای FilledStacks و Reso Coder مدلهای معماری بسیار کارآمدی برای مبتدیها قرار گرفته است. البته در طول زمان این معماریها با تغییراتی همراه بودهاند و همین موضوع باعث شده است که درک آن برای افراد مبتدی مقداری سخت باشد.
هدف ما در این مقاله این است که شما به عنوان یک برنامه نویس مبتدی فلاتر به درک بالایی از معماری اپلیکیشن های فلاتر برسید و سپس یک مثال عملی را با کمک Stacked Package وبسایت FilledStacks اجرا خواهیم کرد.
نگاهی کلی به معماری FilledStacks
این وبسایت از یک معماری با سبک MVVM استفاده میکند. در این نوع معماری نما (View) معمولا یک ویجت است که در یکی از صفحات نرمافزار شما قرار گرفته است. البته این ویجت هیچگونه منطق یا حالتی ندارد. همچنین این ویجت در View Model قرار گرفته است و هیچ اطلاعاتی هم از نما ندارد. اکثر نرمافزارها به نگهداری و ذخیرهی اطلاعات نیاز دارند. این کار توسط لایهی سرویسها انجام میشود. این لایه عملا همان کلاسهای دارت هستند که جزئیات را در بر میگیرند تا مدلهای نما نیازی نباشد نگران آنها باشند.
برای هر کدام از صفحات وبسایت خودتان باید یک مدل نما ایجاد کنید اما مدل سرویسها برای تمامی نماها قابل دسترسی است و هیچ تغییری نمیکند. ساختار یک اپلیکیشن چند صفحهای براساس معماری مقدماتی فلاتر چیزی شبیه به تصویر زیر خواهد بود:
در عمل هم فایلهای شما ممکن است ساختاری مثل شکل زیر داشته باشند:
البته شما مختار هستید که فایلهای خودتان را به هر شکل دیگری نگهداری کنید اما FiledStacks توصیه میکند مدلهای نما در پوشهی Core قرار بگیرند. همچنین شما میتوانید ساختار فایلهای خود را هر زمانی که نیاز داشتید هم تغییر دهید و هیچگونه محدودیتی برای این موضوع وجود ندارد.
مثال
در ادامه با یک مثال خیلی ساده نحوهی اجرای معماری بالا را با هم بررسی خواهیم کرد. یاد میگیریم که چطور میتوان یک نما و مدل نمای جدید ایجاد کرد.
نکته: بهتر است این مثال را خودتان هم اجرا کنید و فقط به خواندن آن بسنده نکنید. این کار کمک خواهد کرد درک بهتری از مفهوم معماری مقدماتی فلاتر داشته باشید.
شروع کار
در ابتدا یک پروژه جدید ایجاد کنید و اسم آن را هر چیزی که دوست دارید بگذارید. به صورت پیشفرض نرمافزار شمارنده برای شما ایجاد خواهد شد. لازم است در این نرمافزار تغییراتی ایجاد کنیم تا بتوانیم از سبک معماری MVVM به کمک پکیج Provider Architecture استفاده کنیم.
قبل از هر کاری در pubspec.yaml پکیج Stacked را اضافه کنید:
dependencies:
stacked: ^1.6.0
نکته: برای ایجاد این الگوی معماری لزوما نیازی به استفاده از پکیج Stacked ندارید اما این پکیج برخی از کدهای غیرضروری را حذف میکند و برخی از پیچیدگیهای پکیج Provider را هم مخفی میکند.
در این مثال ما ساختار پوشههایی که در بخش بالا دیدیم را به صورت کامل ایجاد نخواهیم کرد اما شما میتوانید در صورت نیاز این کار را انجام دهید.
مدل نما (View Model)
در پوشهی lib/ یک فایل جدید با اسم Counter_viewmodel.dart ایجاد کنید و سپس کدی که در ادامه مشاهده میکنید را داخل آن قرار دهید:
import 'package:flutter/foundation.dart';
class CounterViewModel extends ChangeNotifier { // <-- extends ChangeNotifier
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
notifyListeners(); // <-- notify listeners
}
}
توجه داشته باشید که این کلاس از ChangeNotifier ارث برده است و این موضوع باعث شده است متد notifyListeners() در اختیار شما قرار بگیرد. نمای پیش رو یک شنونده یا listener است و به همین خاطر این متد دقیقا چیزی خواهد بود که پکیج Stacked از آن برای بازسازی رابط کاربری نرم افزار پس از اعمال تغییرات استفاده میکند. ChangeNotifier هم بخشی از زیرساخت فلاتر است و به همین خاطر هیچگونه وابستگی به پکیجهای Stacked یا Provider نداریم.
نما (View)
در پوشهی lib/ یک فایل جدید با اسم counter_screen.dart ایجاد کنید. این فایل همان ویجت نمای شما برای صفحهی شمارنده خواهد بود. پس از ایجاد این فایل، کد زیر را داخل آن قرار دهید:
import 'package:flutter/material.dart';
import 'package:flutter_architecture_example/counter_viewmodel.dart';
import 'package:provider_architecture/provider_architecture.dart';
// Since the state was moved to the view model, this is now a StatelessWidget.
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// ViewModelProvider is what provides the view model to the widget tree.
return ViewModelProvider<CounterViewModel>.withConsumer(
viewModel: CounterViewModel(),
builder: (context, model, child) => Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${model.counter}', // <-- view model
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
model.increment(); // <-- view model
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
),
);
}
}
در قسمت بالای متد build() شما میتوانید ViewModelBuilder را مشاهده کنید. این همان چیزی است که CounterViewModel را به درختچه ویجتها اضافه میکند. به دلیل اینکه حالت در مدل نما قرار دارد هم نیازی به استفاده از ویجتهای با حالت نداریم. با این وجود میبینید که CounterScreen یک ویجت با حالت است. این ویجت مقدار Counter را از مدل نما دریافت میکند و زمانی که کلید روی صفحه فشار داده شود، متد increment() مدل نما را فراخوانی خواهد کرد. در نهایت این عمل باعث اجرای یک بازسازی با مقدار Counter میشود.
در نهایت باید در فایل Main.dart هم تغییراتی ایجاد کنید. شما میتوانید کدهای موجود در این فایل را با قطعه کد زیر جایگزین کنید:
import 'package:flutter/material.dart';
import 'counter_view.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CounterScreen(),
);
}
}
در صورتی که اکنون کد خودتان را اجرا کنید، عملکرد آن دقیقا شبیه نرمافزار پیش فرض خواهد بود:
مزایای Provider Architecture Package
تا اینجا ما از یک پکیج Provider قدیمی به همراه ChangeNotifierProvider و Consumer استفاده کردهایم اما خیلی از اوقات ممکن است نیاز باشد از اینترنت یا یک دیتابیس تحت شبکه اطلاعاتی را دریافت کنیم. مدل نمای شما میتواند یک متد را برای دریافت این اطلاعات در اختیار شما قرار دهد.
برای انجام این کار کدهای موجود در counter_viewmodel.dart را با کد زیر جایگزین کنید:
import 'package:flutter/foundation.dart';
class CounterViewModel extends ChangeNotifier {
int _counter = 0;
int get counter => _counter;
WebApi _webApi = serviceLocator<WebApi>(); // <-- service
Future loadData() async { // <-- load initial data
_counter = await _webApi.fetchValue();
notifyListeners();
}
void increment() {
_counter++;
notifyListeners();
}
}
// Fake service locator. Use GetIt in a real app.
// Or inject the service in the view model constructor.
WebApi serviceLocator<T>() {
return WebApi();
}
// Fake web api
class WebApi {
Future<int> fetchValue() => Future.delayed(Duration(seconds: 2), () => 11);
}
همانطور که مشاهده میکنید اکنون یک متد برای دریافت برخی اطلاعات پایه از یک سرویس API تحت وب در اختیار شما قرار گرفته است. در این مقاله نمیتوانیم در مورد ایجاد سرویسهای مختلف صحبت کنیم، به همین خاطر از یک کد تقلبی در پایان صفحه استفاده کردهایم.
نکته جالب در مورد ViewModelBuilder این است که یک کال بک با عنوان onModelReady دارد که به شما اجازه میدهد زمانی که مدل نما آماده است برخی مقداردهیهای اولیه را داشته باشید.
در فایل counter_screen.dart مقدار زیر را به ViewModelBuilder اضافه کنید:
onModelReady: (model) => model.loadData(),
اکنون زمانی که نرمافزار را اجرا کنید، پس از مدت زمان ۲ ثانیهای به صورت خودکار به روزرسانی خواهد شد.
سخن نهایی
در این مقاله تلاش شد شما به صورت کامل با معماری اپلیکیشن فلاتر آشنا شوید و تمامی مسائل مربوط به آن را به صورت عملی یاد بگیرید. امیدواریم که این مقاله برای شما مفید واقع شده باشد.