فلاتر

آموزش مدیریت State در فلاتر با الگوی بلاک Bloc + ویدیو

با بزرگ و بزرگ تر شدن پروژه و پیچیده شدن کلاس ها مدیریت کردن State تبدیل به کار دشواری می شود و زمانی که بخواهید قابلیتی به برنامه کم و زیاد کنید زمان زیادی را از دست میدهید و باگ های مختلفی در برنامه ایجاد می شود.

در برنامه نویسی فلاتر یکی از الگوهای طراحی پر کاربرد الگوی Bloc می باشد.

قبلا در مورد Bloc به شکل مختصر در مطلب زیر صحبت کرده بودیم.

روش های مدیریت State در فلاتر

در این مطلب بصورت تئوری با Bloc آشنا میشیم و در ویدیو بالا هم بصورت عملی نحوه پیاده سازی این الگوی برنامه نویسی در فلاتر را بررسی می کنیم.

بلاک چیست؟

در واقع Bloc شکل کوتاه شده Business Logic Components هست که در سال 2018 توسط گوگل به عنوان روشی برای مدیریت State معرفی شد. این الگو به ما اجازه میدهد از یک مکان مشخص به State ها دسترسی داشته باشیم.

برای درک بهتر نحوه کار Bloc میتونید به دیاگرام زیر نگاه کنید تا با روند کار جریان اطلاعات از صفحه رابط کاربری تا بخش دیتا آشنا شوید.

آموزش bloc فلاتر

Bloc هیچگونه رفرنسی به ویجت های داخل رابط کاربری ندارد و همچنین ویجت های رابط کاربری هم فقط نمایش دهنده تغییرات اطلاعات Bloc هستند.

همانطور هم که احتمالا خودتون متوجه شدید این الگو بسیار شبیه به الگوهای MVVM,MVP هست, برای مثال ViewModel با Bloc جایگزین شده است.

روند کار الگوی بلاک وابسته به رویدادهایی است که ایجاد میشود. این رویداد ها از نوع Stream هستند.

در هر زمانی که نیازی به تغییر اطلاعات یا همان State باشد یک رویداد که به آن Event میگوییم از طریق کلاس بلاک ایجاد میشود و براساس نوع رویداد عملیات مورد نظر انجام شده و نتیجه به کلاس State که طراحی کرده ایم ارسال میشود.

در این لحظه ویجت های مورد نظر اطلاعات جدید را از کلاس State دریافت میکنند و به کاربر نمایش میدهند.

Stream چیست؟

برای درک نحوه کار بلاک لازم هست که از استریم ها اطلاعات کافی داشته باشید.

به جریان مداوم از هر چیزی استریم میگوییم. در اینجا هم منظور یک جریان همیشگی از اطلاعات می باشد.

ویژگی های مهم استریم شامل موارد زیر می باشد.

  • StreamController: وظیفه کنترل استریم را برعهده دارد.
  • StreamTransformer: عملیاتی که روی ورودی ها انجام می شود.
  • StreamBuilder: این متد یک استریم به عنوان ورودی میگیرد و با استفاده از بخش builder میتونیم ویجت هایی که میخواهیم در زمان تغییر اطلاعات آپدیت شوند را داخلش قرار دهیم.
  • subscription: مشخص کننده نوع استریم است. هر استریم به دو نوع کلی تقسیم میشود.

    بصورت پیش فرض هر استریم در زمان ساخته شدن از نوع single subscription است یعنی تنها یک آبجکت یا ویجت میتواند در لحظه به تغییرات این استریم گوش دهد یا اطلاعات جدید را دریافت کند.

    اما نوع دیگری نیز وجود دارد به نام broadcast که بینهایت ویجت یا آبجکت به اطلاعات این استریم میتوانند دسترسی داشته باشند.

پیاده سازی الگوی بلاک

در این ویدیو باهمدیگه یاد میگیریم که چگونه از طریق کتابخانه های bloc و flutter_bloc برای پیاده سازی الگوی بلاک کمک بگیریم.

با استفاده از یک پروژه عملی نحوه کار Bloc و به صورت کامل بررسی میکنیم.

پیوست: در ویدیو بالا در انتهای پروژه در فایل main کد زیر را قرار دهید.

@override
  void dispose() {
    _bloc.dispose();
  }

حتما در متد dispose باید آبجکت بلاک خودمون را dispose کنیم.


شروع کار با بلاک

برای استفاده از الگوی بلاک نیاز به دو پکیج bloc و flutter_bloc دارید که همانند دیگر پکیج ها به راحتی آنها را نصب کنید.

flutter pub add flutter_bloc
flutter pub add bloc

در ادامه نیاز به ایجاد چند کلاس مختلف خواهیم داشت.

کلاس اول مربوط به رویدادهای مورد نیاز میباشد که به عنوان اینترفیس عمل میکنند و هر زمان نیاز به یک رویداد بود ایترفیس مورد نظر را فراخوانی میکنیم.

مثالی که بررسی میکنیم مشابه پروژه پیشفرض فلاتر است که با کلیک کردن روی یک دکمه مقدار عدد مورد نظر افزایش پیدا میکند.

abstract class CounterEvent {}

class Increment extends CounterEvent {}

کلاس CounterEvent کلاس رویداد اصلی ما است و باقی رویدادهای ما نیز باید از این کلاس ارث بری کنند. البته رویدادهایی که برای کلاس بلاک در ادامه خواهیم ساخت. هر صفحه از اپلیکیشن میتواند شامل یک کلاس بلاک و رویداد جدا باشد.

هر زمان که نیاز بود یک مقدار به عدد مورد نظر اضافه کنیم این رویداد را صدا خواهیم زد.

در ادامه کلاس bloc برنامه را ایجاد میکنیم.

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<Increment>((event, emit) => emit(state + 1));
  }
}

این کلاس حتما باید از کلاس Bloc ارث بری کند و سپس کلاس رویداد که و کلاس State مشخص شود.

در این مثال چون State پیچیده ای نداریم از نوع داده int به عنوان State استفاده میکنیم و کلاس جداگانه ای برای آن ایجاد نمیکنیم.

در متد سازنده با فراخوانی متد super مقدار پیش فرض state را برابر عدد صفر قرار میدهیم.

در ادامه یک متد ایجاد میکنیم که نوع آن برابر است با نام رویدادی که پیش تر ساخته بودیم. این متد هربار که این رویداد صدا زده شود اجرا میشود و وظیفه آن اضافه کردن یک واحد به State میباشد.

اما نوبت به نحوه استفاده از این کلاس رسیده است.

Future<void> main() async {
  final bloc = CounterBloc();


  bloc.add(Increment());

  await Future.delayed(Duration.zero);

  print(bloc.state); 

  await bloc.close();
}

ابتدا نیاز است تا یک نمونه از کلاس بلاک ایجاد کینم.

برای ایجاد یک رویداد از متد add کمک میگیریم و به عنوان پارامتر ورودی رویداد مورد نظر را قرار میدهیم.
این دستور را میتوانیم برای مثال در بخش کلیک یک دکمه قرار دهیم که با کلیک کردن کاربر روی آن رویداد افزایش یک واحد عدد ایجاد شده و کلاس بلاک آن را انجام میدهد.

برای نمایش اطلاعات هم از نمونه ساخته شده ویژگی state را فراخوانی میکنیم.

اگر بخواهیم اطلاعات را درون یک ویجت نمایش دهیم که با هربار تغییرات نیز اطلاعات به روز شده را نمایش دهد بصورت زیر عمل میکنیم.

BlocBuilder<CounterBloc, int>(
  builder: (context, state) {
    return Text('Count: $state');
  },
)

در ویجت BlocBuilder ابتدا کلاس بلاک و سپس نوع State را مشخص میکنیم که میتوانیم برای آن هم یک کلاس جداگانه طراحی کنیم.

سپس در متد builder طراحی مورد نظر خود را انجام میدهیم که برای این بخش یک ویجت Text قرار داده ایم.

طراحی کلاس State

بله، برای مدیریت وضعیت در الگوی BLoC در فلاتر، معمولاً از کلاس جداگانه‌ای برای نگهداری وضعیت (state) استفاده می‌شود. در اینجا یک مثال از ایجاد یک کلاس جداگانه برای نگهداری وضعیت برای یک نمونه ساده از شمارنده (counter) آورده شده است:

import 'package:equatable/equatable.dart';

abstract class CounterState extends Equatable {
  final int count;

  const CounterState(this.count);

  @override
  List<Object?> get props => [count];
}

class InitialCounterState extends CounterState {
  const InitialCounterState(int count) : super(count);
}

class IncrementedCounterState extends CounterState {
  const IncrementedCounterState(int count) : super(count);
}

class DecrementedCounterState extends CounterState {
  const DecrementedCounterState(int count) : super(count);
}

ابتدا یک کلاس پایه داریم که سایر کلاس ها باید از کلاس ارث بری کنند و با توجه به وضعیتی که در برنامه رخ میدهد اطلاعات مختلف را بازگردانی کنند.

به عنوان مثال، وضعیت اولیه برای شروع و وضعیت‌هایی برای افزایش و کاهش شمارنده.

حالا که کلاس State تعریف شد، می‌توانیم از آن در کلاس BLoC استفاده کنیم:

import 'package:bloc/bloc.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(InitialCounterState(0));

  @override
  Stream<CounterState> mapEventToState(CounterEvent event) async* {
    if (event is IncrementEvent) {
      yield IncrementedCounterState(state.count + 1);
    } else if (event is DecrementEvent) {
      yield DecrementedCounterState(state.count - 1);
    }
  }
}

همچنان نیاز به تعریف رویدادها یا همان Eventها داریم:

abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}

class DecrementEvent extends CounterEvent {}

در ویو، از BLoC استفاده می‌کنیم تا وضعیت را مدیریت کنیم و به تغییرات وضعیت واکنش نشان دهیم:

BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    return Text('Count: ${state.count}');
  },
)

این روش به شما امکان می‌دهد که وضعیت برنامه را با استفاده از کلاس‌های جداگانه مدیریت کنید و از مزایای الگوی BLoC برای مدیریت وضعیت در فلاتر بهره‌برداری کنید.

در بخش BlocBuilder از کتابخانه flutter_bloc، شما می‌توانید بر اساس وضعیت‌های مختلف که از BLoC دریافت می‌کنید، ویجت‌های مختلف فلاتر را نمایش دهید.

برای این کار، می‌توانید از builder که به عنوان یک تابع برگشتی به BlocBuilder پاس داده می‌شود، استفاده کنید و بر اساس وضعیت‌های مختلف ویجت‌های متفاوتی ایجاد کنید. در زیر، یک مثال از چگونگی انجام این کار آمده است:

BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    if (state is InitialCounterState) {
      return Column(
        children: [
          Text('Count: ${state.count}'),
          ElevatedButton(
            onPressed: () {
              // Dispatch an event to increment the counter
              context.read<CounterBloc>().add(IncrementEvent());
            },
            child: Text('Increment'),
          ),
        ],
      );
    } else if (state is IncrementedCounterState) {
      return Column(
        children: [
          Text('Count: ${state.count}'),
          ElevatedButton(
            onPressed: () {
              // Dispatch an event to decrement the counter
              context.read<CounterBloc>().add(DecrementEvent());
            },
            child: Text('Decrement'),
          ),
        ],
      );
    } else if (state is DecrementedCounterState) {
      return Column(
        children: [
          Text('Count: ${state.count}'),
          ElevatedButton(
            onPressed: () {
              // Dispatch an event to increment the counter
              context.read<CounterBloc>().add(IncrementEvent());
            },
            child: Text('Increment'),
          ),
        ],
      );
    }
    return Container(); .
  },
)

پیاده سازی الگوی Cubit در فلاتر

پکیج بلاک فلاتر شامل روش دیگری برای مدیریت حالت به نام Cubit نیز میشود.

Cubit و BLoC هر دو الگوهای معماری برای مدیریت وضعیت (state management) در فریم‌ورک فلاتر هستند، اما دارای تفاوت‌های مهمی هستند:

  1. معماری و نحوه کارکرد:
    • BLoC (Business Logic Component): BLoC یک الگوی معماری کاملتر است که علاوه بر مدیریت وضعیت، برای مدیریت رویدادها و ارتباط با سرویس‌ها و منابع داده نیز استفاده می‌شود. BLoC از Stream یا RxDart برای ارسال و دریافت داده‌ها استفاده می‌کند.
    • Cubit: Cubit یک سیستم ساده‌تر و کوچکتر از BLoC است.
      معمولاً برای مدیریت وضعیت استفاده می‌شود و فقط یک مقدار وضعیت را تعیین می‌کند. Cubit از Stream استفاده نمی‌کند و به جای آن با استفاده از emit مقدار وضعیت را تغییر می‌دهد.

  2. کدگذاری و پیچیدگی:
    • BLoC: BLoC به عنوان یک الگوی معماری کامل برای برنامه‌های پیچیده تر مناسب است. این الگو به شما امکان می‌دهد که منطق کسب و کار را از ویو جدا کنید و مدیریت دقیق‌تری بر روی وضعیت و رویدادها داشته باشید. این امکان را داراست که چندین BLoC در برنامه‌تان داشته باشید.
    • Cubit: Cubit به عنوان یک راه حل ساده‌تر و کوچکتر برای برنامه‌های کوچکتر و ساده‌تر مناسب است. این به شما کمک می‌کند که به سادگی وضعیت را مدیریت کنید و از پیچیدگی‌های اضافی در برنامه خود دوری بیافتید.
  3. کتابخانه‌های مورد نیاز:
    • BLoC: BLoC برای مدیریت وضعیت و رویدادها معمولاً از کتابخانه‌هایی مانند bloc و flutter_bloc استفاده می‌کند.
    • Cubit: Cubit معمولاً از کتابخانه flutter_bloc برای مدیریت وضعیت استفاده می‌کند، اما به صورت کلی کوچکتر و ساده‌تر از BLoC است و نیاز به کتابخانه‌های کمتری دارد.

به طور خلاصه، اگر برنامه‌تان کوچک و ساده باشد، Cubit ممکن است گزینه‌ی بهتری باشد، اما اگر برنامه‌تان پیچیده‌تر باشد و نیاز به مدیریت دقیق‌تر وضعیت و رویدادها داشته باشید، BLoC یک گزینه قدرتمندتر و انعطاف‌پذیرتر است. انتخاب بین این دو به نیاز‌ها و پیچیدگی برنامه‌تان بستگی دارد.

در مثال زیر الگوی Cubit را پیاده سازی میکنیم.

ابتدا یک Cubit برای مدیریت وضعیت ایجاد کنید:

import 'package:flutter_bloc/flutter_bloc.dart';

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);

  void decrement() => emit(state - 1);
}

در ویو، BlocProvider را به عنوان والد ویجت‌ها ایجاد کنید و CounterCubit را در آن قرار دهید:

BlocBuilder<CounterCubit, int>(
  builder: (context, state) {
    return Column(
      children: [
        Text('Count: $state'),
        ElevatedButton(
          onPressed: () {
            context.read<CounterCubit>().increment();
          },
          child: Text('Increment'),
        ),
        ElevatedButton(
          onPressed: () {
            context.read<CounterCubit>().decrement();
          },
          child: Text('Decrement'),
        ),
      ],
    );
  },
)

در این روش هم همانند حالت قبل پیشنهاد میشود که یک کلاس جداگانه برای State ایجاد کنید. روش استفاده مشابه همدیگر است.

این دو روش در کنار سایر روش های مدیریت State در فلاتر کاربردهای بسیاری دارند و این بستگی به شما و نوع پروژه دارد که از کدام یک در نهایت استفاده کنید.

روش بلاک در ترکیب با معماری تمیز Clean Architecture در فلاتر نیز در بسیاری از پروژه ها استفاده میشود.

Hesam

View Comments

  • سلام خسته نباشید
    مرسی برای این ویدیوی آموزشی
    خیلی قشنگ توضیح دادید واقعا خسته نباشید
    لطفا تو یک ویدیو دیگه آموزش بدید که چطور می تونیم داخل یک bloc چندین state داشته باشیم
    ممنونم

    • سلام
      ممنون, حتما ویدیو های بیشتری در آینده گذاشته میشه.
      شما میتونید بجای تعریف یدونه فیلد int current چندین نوع فیلد دیگه هم تعریف کنید.

    • سلام
      میشه ی مثال هم وقتی که ریپازیتوری لوکال و ریموت داریم هم با bloc بذارید
      ممنون

    • سلام, bloc برای مدیریت state کاربرد داره اگر پروژه کوچیکی دارید استفاده از bloc کفایت میکنه ولی پروژتون اگر متوسط به بالا هست ترکیب bloc و معماری های دیگه میتونه گزینه مناسبی باشه.

  • سلام خیلی ممنون واقعا قشنگ توضیح میدید خلاصه ومفید فقط اگه ممکنه با ورژن جدید یه مثال حتی اگه شده متن نه ویدیو بذارید چون تو ورژن جدید تو قسمت bloc عوض شده
    Stream mapEventToState(
    UserEvent event,
    ) async* {}
    همینطور که میبینید کنار event نمیشه currentState گذاشت تا بهشون دسترسی داشته باشیم

    • سلام
      تو دوره جدیدی که در حال ضبط هست از ورژن 1.0 بلاک استفاده شده و تمام این ها بررسی میشه, برای بقیه دوستان هم در همین پست یا پست جدیدی نحوه کار کردن با ورژن جدید و حتما آموزش خواهیم داد.

  • سلام مهندس حسام.
    لینک ویدیو مشکل داره و باز نمیشه...
    من هر 2 دوره ی آموزشیتون رو دیدم خیلی خوب بود
    لطفا یک دوره اختصاصی فقط در مورد طراحی اپلیکیشن با bloc و RxDart و Dio بزارین که نحوه صحیح کار با stream و ارتباط اینترنتی رو در معماری bloc یاد بگیریم...
    مشکل لینک ویدیو رو هم رفع کنید ...
    ممنون

  • سلام
    ممنون بخاطر آموزش خوبتون اگه امکانش هست مثال هایی پیشرفته تر با event بیشتر در مورد Bloc قرار بدید

    • سلام خواهش میکنم, برای آموزش های بیشتر درباره بلاک میتونید از دوره "طراحی اپلیکیشن وردپرس استفاده کنید"

  • سلام دوست عزیز
    بسیار بسیار سپاسگزارم از توضیحات و آموزش این بحث
    نکته ای که دارم اینکه در این زمان که من دارم این موضوع را بررسی می کنم فکر میکنم (البته مطمعن نیستم) احتمالا تغییراتی در ساختار فلاتر بوجود اومده که دیگه تابع mapEventToState پارامتر State را قبول نمی کنه و فقط مجاز به دریافت Event هست اگر اینطوره لطفا در تغییرات احتمالی را بشکلی مشخص کنید.

    با سپاس
    امیر
    http://www.amirhome.com

    • سلام, بله از نسخه 1.0 به بعد این تابع بصورت یک پارامتر ورودی فقط پیاده سازی میشه که در این مطلب میتونید نحوه استفاده از اون رو ببینید.
      آموزش بلاک 1.0

Recent Posts

گیتهاب اکشن چیست؟ آموزش استفاده از گیتهاب اکشن در برنامه نویسی فلاتر

گیتهاب اکشن GitHub Actions یکی از ابزارهای گیتهاب است که به شما کمک می‌کنه تا…

9 ساعت ago

آموزش افزایش سرعت اجرای وب اپلیکیشن های فلاتر

اگر یک برنامه نویس فلاتر هستید و با از نسخه وب اپلیکیشن پروژتون استفاده میکنید…

4 هفته ago

آموزش جامع انتشار اپلیکیشن اندروید و فلاتر در فروشگاه گوگل پلی Google play

به عنوان یک برنامه نویس فلاتر یا اندروید بعد از اتمام پروسه طراحی اپلیکیشن نیاز…

2 ماه ago

دانلود سورس کد رابط کاربری اپلیکیشن فلاتر پروژه پادکست

طراحی رابط کاربری اپلیکیشن پادکست خود را با استفاده از این کیت توسعه UI/UX فلاتر…

2 ماه ago

فایربیس چیست؟ معرفی سرویس ابری Firebase و کاربردهای آن

فایربیس، پلتفرمی قدرتمند از شرکت گوگل برای توسعه و مدیریت برنامه‌های موبایل و وب است.…

2 ماه ago

آموزش پیاده سازی Method Channel در فلاتر + فیلم

فلاتر یک فریم ورک برنامه نویسی چندسکویی است که این امکان را برای برنامه نویس…

3 ماه ago