آموزش مدیریت State با بلاک
آموزش مدیریت State با Provider
آموزش مدیریت State با Riverpod
آموزش مدیریت State با Redux
روش های مدیریت State در فریمورک هایی چون Flutter و React Native همیشه یکی از داغ ترین بحث هاست و افراد نظرات و شیوه های مختلفی برای انجام این کار دارند.
در این مطلب هدف ما بررسی روش های مدیریت State در فریمورک فلاتر هست.
الگو و پکیج های مختلفی برای انجام این کار وجود داره و بعضی وقت ها افراد فرض میکنند که هرچقدر راه طولانی تر و سخت تری را طی کنند بهتر میتونند مدیریت State و پیاده سازی کنند اما فقط باعث افزایش پیچیدگی پروژه می شود. اما گاهی اوقات هم نتایج بسیار خوبی دربر خواهد داشت.
پس توصیه میکنم که همیشه متکی به یک راه و روش یا کتابخانه نباشید و پروژه و نیازمندی های خودتون و بررسی کنید و سپس بهترین گزینه و انتخاب کنید.
اول از همه شاید بهتر باشه برای افرادی که هنوز مدت زمان زیادی نیست که با برنامه نویسی فلاتر آشنا شده اند توضیح بدیم که State به شکل ساده چی هست.
به اطلاعاتی که در طول چرخه حیات یک اپلیکیشن تغییر میکنند State میگیم.
به طور کلی دو نوع State وجود دارد.
نوع اول State های محلی هستند که مختص به یک ویجت هستند و فقط یک ویجت از این اطلاعات استفاده میکند.
نوع دوم App State نام دارد که به اطلاعاتی گفته میشود که تعداد زیادی از ویجت ها از آن استفاده میکنند.
رابط کاربری برنامه شما در واقع نمایشی از این State ها می باشد و وقتی مقدار یک State را تغییر می دهید ویجت ها و رابط کاربری اپلیکیشن دوباره از نو ساخته می شود.
همزمان با اینکه پروژه شما رفته رفته بزرگ تر و پیچیده تر میشه شما با باگ هایی برخورد میکنید که مستقیما به دلیل جریان ورود اطلاعات توسط کاربر یا منابع دیگه می باشد.
مدیریت صحیح روش های بروزرسانی State باعث میشه که کمتر با خطاهایی در زمان اجرای برنامه مواجه شوید و کارایی اپلیکیشن بهبود پیدا می کند.
فلاتر بصورت پیش فرض یک سری پکیج داخل خودش داره که برای مدیریت State میتونیم ازشون استفاده کنیم که شامل موارد زیر هست:
علاوه بر این ابزارهای مختلف دیگه مثل RxDart و Bloc هم به عنوان موارد خارجی کاربرهای زیادی برای ما دارند.
اگر در یک صفحه از اپلیکیشن خودتون نیاز به تغییرات اطلاعات ندارید و صرفا حالت نمایشی دارند محتویات, میتونید تمام State ها را داخل یک ویجت قرار دهید.
اما به تصویر زیر نگاه کنید, اگر نیاز دارید تا اطلاعاتی و از ویجت های فرزند به ریشه درخت ارسال کنید در این صورت نیاز به یکی از روش های مدیریت State دارید.
در این ویجت با استفاده از دستور SetState میتونید مقادیر خودتون و هروقت که نیاز بود تغییر بدید.
در واقع اولین و ساده ترین راه برای مدیریت state همین روش می باشد.
StatefulWidget نیاز به دو کلاس مختلف و مقداری کدنویسی اضافه داره که باعث گرفته شدن وقت میشه و راه های آسان تری هم برای پیاده سازی این روش وجود داره که باهم دیگه بررسی میکنیم.
استفاده از StatefulBuilder میتونه جایگزین بهتری باشه در برخی مواقع.
class MyHomePage2 extends StatelessWidget {
int _counter = 0;
@override
Widget build(BuildContext context) {
return StatefulBuilder(
builder: (ctx, StateSetter setState) =>
Scaffold(
body:Text('$_counter'),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => _counter++),
),
)
);
}
}
این ویجت به تمام ویجت های فرزند خودش یک context اختصاص میده و با استفاده از آن میتونید تغییراتی که نیاز دارید و اعمال کنید.
این روش هم نیاز به کدنویسی زیادی نسبت به بقیه روش ها داره که باعث میشه افراد زیادی استفاده نکنند.
و از روش های دیگه مثل Bloc, Redux, ,Riverpod,Scoped Model استفاده کنند.
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: InheritedCounter( child: MyHomePage3() ), // <-- make sure your
InheritedWidget wraps the widgets that use its data
);
}
}
// The InheritedWidget
class InheritedCounter extends InheritedWidget {
final Map _counter = { 'val': 0 }; // Data structure is a
map because InheritedWidgets are immutable
final Widget child;
InheritedCounter({ this.child }) : super(child: child);
increment() {
_counter['val']++;
}
get counter => _counter['val'];
@override
bool updateShouldNotify(InheritedCounter oldWidget) => true;
static InheritedCounter of(BuildContext context) =>
context.inheritFromWidgetOfExactType(InheritedCounter);
}
class MyHomePage3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
int counter = InheritedCounter.of(context).counter;
Function increment = InheritedCounter.of(context).increment;
return Scaffold(
body: Text('$counter'),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => increment()),
),
);
}
);
}
}
این روش یکی از بهترین راه ها برای مدیریت State های global می باشد که انعطاف پذیری بالایی دارد و به خوبی نیازهای شما را رفع می کند.
BehaviorSubject ویژگی و قابلیت های متنوعی دارد که باعث میشود به ابزاری مناسب برای مدیریت State تبدیل شود.
در زیر باهم دیگه به بررسی یک مثال میپردازیم که قصد داریم از عدد صفر شروع به شماردن کنیم.
در واقع مقدار current value را میخونیم و سپس یک عدد به آن اضافه میکنیم.
کلاس Counter بخش منطقی برنامه و ایجاد میکند و همچنین یک state که از همه جا به آن دسترسی داریم.
در واقع در این روش از الگوی Observable استفاده میکنیم و stream$ را به StreamBuilder
پاس میدیم تا هروقت مقداری جدیدی داخل آن قرار گرفت ویجت ما هم آپدیت بشه.
import 'package:rxdart/rxdart.dart';
// Global Variable
Counter counterService = Counter();
// Data Model
class Counter {
BehaviorSubject _counter = BehaviorSubject.seeded(0);
Observable get stream$ => _counter.stream;
int get current => _counter.value;
increment() {
_counter.add(current + 1);
}
}
// StreamBuilder Widget
class MyHomePage4 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: counterService.stream$,
builder: (BuildContext context, AsyncSnapshot snap) {
return Text('${snap.data}');
}
),
floatingActionButton: FloatingActionButton(
onPressed: () => counterService.increment(),
),
);
}
}
تنها مشکلی که روش بالا دارد این هست که از یک متغیر سراسری برای اشتراک گذاری state استفاده می کند.
روش BLoC تقریبا شبیه به InheritedWidget می باشد اما مرتب تر و قابل بسط دادن می باشد.
برای این که بتونید راحت تر این نوع الگو را پیاده سازی کنید میتونید از پکیج flutter_bloc استفاده کنید.
روش پیاده سازی تقریبا شبیه به Redux می باشد که شامل موارد زیر هست.
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
enum CounterEvent { increment } // 1
class CounterBloc extends Bloc<counterevent, int=""> { // 2
@override
int get initialState => 0;
@override
Stream<int> mapEventToState(int currentState, CounterEvent event) async* {
switch (event) {
case CounterEvent.increment:
yield currentState + 1;
break;
}
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider<counterbloc>( // 3
bloc:CounterBloc(),
child: MyHomePage5()
),
);
}
}
class MyHomePage5 extends StatelessWidget {
@override
Widget build(BuildContext context) {
final CounterBloc _counterBloc = BlocProvider.of<counterbloc>(context); // 4
return Scaffold(
appBar: AppBar(
title: Text('BLoC'),
),
body: BlocBuilder( // 5
bloc: _counterBloc,
builder: (BuildContext context, int count) {
return Text(
'${count}',
);
}
),
floatingActionButton: FloatingActionButton(
onPressed: () => _counterBloc.dispatch(CounterEvent.increment), // 6
),
);
}
}
به غیر از موارد بالا که بررسی کردیم ابزارها و کتابخانه های مختلف دیگری برای مدیریت State در فلاتر وجود دارند که مهم ترین آنها شامل موارد زیر هست.