~/icsd.ir — bash
SYSTEM_ONLINE

State Management

State Management مهم‌ترین مفهوم در Flutter پس از Widget است. در این فصل با همه روش‌های اصلی آشنا می‌شویم: setState، Provider، Riverpod، Bloc و GetX.

۶.۱ مقدمه

State Management مهم‌ترین مفهوم در Flutter پس از Widget است. در این فصل با همه روش‌های اصلی آشنا می‌شویم: setState، Provider، Riverpod، Bloc و GetX.

هدف فصل: درک کامل state management و انتخاب صحیح بر اساس نیاز پروژه.

۶.۲ State چیست؟

State هر اطلاعاتی است که در طول زمان تغییر می‌کند و UI به آن وابسته است.

دو نوع state

  • Ephemeral State (محلی): فقط یک widget آن را می‌داند. مثل expanded بودن یک accordion، صفحه فعلی PageView.
  • App State (سراسری): چندین widget یا صفحه به آن نیاز دارند. مثل user اطلاعات، سبد خرید، تم.

قانون انتخاب

  • اگر فقط در یک widget لازم است → setState
  • اگر در ۲-۳ widget مرتبط لازم است → InheritedWidget یا Provider
  • اگر در سراسر اپ لازم است → Riverpod، Bloc، یا Redux

۶.۳ setState — ساده‌ترین راه


class CounterPage extends StatefulWidget {
  const CounterPage({super.key});
  @override
  State createState() => _CounterPageState();
}

class _CounterPageState extends State {
  int _count = 0;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text("$_count")),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => _count++),
        child: const Icon(Icons.add),
      ),
    );
  }
}
    

محدودیت: فقط در همان widget کار می‌کند. برای اشتراک state بین widget ها مناسب نیست.

۶.۴ Lifting State Up

اگر دو widget child به یک state نیاز دارند، state را به parent ببرید.


class ParentWidget extends StatefulWidget {
  @override
  State createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State {
  int counter = 0;
  
  void increment() => setState(() => counter++);
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        DisplayWidget(value: counter),       // child 1
        ButtonWidget(onPressed: increment),  // child 2
      ],
    );
  }
}
    

اگر این الگو در عمق زیاد تکرار شود (props drilling)، باید سراغ Provider/Riverpod برویم.

۶.۵ Provider

Provider رسمی‌ترین solution قبل از Riverpod بود و هنوز پرکاربرد است.


flutter pub add provider
    

// 1. Model با ChangeNotifier
class CartModel extends ChangeNotifier {
  final List _items = [];
  
  List get items => List.unmodifiable(_items);
  int get count => _items.length;
  double get total => _items.fold(0, (sum, p) => sum + p.price);
  
  void add(Product product) {
    _items.add(product);
    notifyListeners();  // ⭐ اعلام تغییر
  }
  
  void remove(Product product) {
    _items.remove(product);
    notifyListeners();
  }
  
  void clear() {
    _items.clear();
    notifyListeners();
  }
}

// 2. Provide در root اپ
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CartModel(),
      child: const MyApp(),
    ),
  );
}

// 3. Consume در widget
class CartBadge extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // روش ۱: context.watch (rebuild در تغییر)
    final cart = context.watch();
    
    return Badge(
      label: Text("${cart.count}"),
      child: const Icon(Icons.shopping_cart),
    );
  }
}

class AddToCartButton extends StatelessWidget {
  final Product product;
  const AddToCartButton({super.key, required this.product});
  
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        // روش ۲: context.read (بدون rebuild)
        context.read().add(product);
      },
      child: const Text("افزودن به سبد"),
    );
  }
}

// روش ۳: Consumer (برای rebuild محدود)
Consumer(
  builder: (context, cart, child) {
    return Text("${cart.count} آیتم");
  },
)

// روش ۴: Selector (rebuild فقط اگر بخش خاصی تغییر کند)
Selector(
  selector: (_, cart) => cart.count,
  builder: (_, count, __) => Text("$count"),
)

// چندین Provider
MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (_) => CartModel()),
    ChangeNotifierProvider(create: (_) => UserModel()),
    Provider(create: (_) => ApiClient()),
  ],
  child: const MyApp(),
)
    

۶.۶ Riverpod — جایگزین مدرن Provider

Riverpod توسط همان توسعه‌دهنده Provider ساخته شده ولی محدودیت‌های Provider را ندارد. توصیه قوی برای پروژه‌های جدید.


flutter pub add flutter_riverpod
flutter pub add --dev riverpod_generator riverpod_annotation
flutter pub add --dev build_runner custom_lint riverpod_lint
    

import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:riverpod_annotation/riverpod_annotation.dart";

part "providers.g.dart";

// 1. Simple Provider
@riverpod
String greeting(Ref ref) => "سلام دنیا";

// 2. State Provider
@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;
  
  void increment() => state++;
  void decrement() => state--;
  void reset() => state = 0;
}

// 3. Future Provider (برای async)
@riverpod
Future> products(Ref ref) async {
  final api = ref.read(apiClientProvider);
  return api.getProducts();
}

// 4. Family (parameterized)
@riverpod
Future product(Ref ref, int id) async {
  final api = ref.read(apiClientProvider);
  return api.getProduct(id);
}

// === استفاده در Widget ===

// در ConsumerWidget
class CounterPage extends ConsumerWidget {
  const CounterPage({super.key});
  
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    
    return Scaffold(
      body: Center(child: Text("$count")),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

// در ConsumerStatefulWidget (اگر state داخلی هم لازم است)
class MyPage extends ConsumerStatefulWidget {
  const MyPage({super.key});
  @override
  ConsumerState createState() => _MyPageState();
}

class _MyPageState extends ConsumerState {
  @override
  Widget build(BuildContext context) {
    final products = ref.watch(productsProvider);
    
    return products.when(
      data: (data) => ListView.builder(
        itemCount: data.length,
        itemBuilder: (_, i) => ProductTile(product: data[i]),
      ),
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (err, stack) => Center(child: Text("خطا: $err")),
    );
  }
}

// 5. Run app با ProviderScope
void main() {
  runApp(const ProviderScope(child: MyApp()));
}

// === متدهای کلیدی ===
ref.watch(provider)        // rebuild در تغییر
ref.read(provider)         // بدون watch (در event handler ها)
ref.listen(provider, ...)  // side effect
ref.invalidate(provider)   // refresh
    

چرا Riverpod بهتر است؟

  • ✅ Compile-time safe (بدون runtime exceptions)
  • ✅ Provider override راحت برای test
  • ✅ بدون نیاز به BuildContext (می‌توان در logic استفاده کرد)
  • ✅ Auto-dispose خودکار
  • ✅ Family برای parameterized providers
  • ✅ Code generation برای تایپ‌های کامل

۶.۷ BLoC / Cubit

BLoC (Business Logic Component) برای پروژه‌های enterprise بسیار محبوب است. سخت‌گیرانه‌ترین جداسازی UI از logic.


flutter pub add flutter_bloc bloc
    

// === Cubit (ساده‌تر از Bloc) ===
class CounterCubit extends Cubit {
  CounterCubit() : super(0);  // initial state
  
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
}

// استفاده
BlocProvider(
  create: (_) => CounterCubit(),
  child: BlocBuilder(
    builder: (context, count) {
      return Column(
        children: [
          Text("$count"),
          ElevatedButton(
            onPressed: () => context.read().increment(),
            child: const Text("+"),
          ),
        ],
      );
    },
  ),
)

// === Bloc (با event ها) ===
// 1. Events
sealed class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
class Reset extends CounterEvent {}

// 2. Bloc
class CounterBloc extends Bloc {
  CounterBloc() : super(0) {
    on((event, emit) => emit(state + 1));
    on((event, emit) => emit(state - 1));
    on((event, emit) => emit(0));
  }
}

// 3. استفاده
BlocProvider(
  create: (_) => CounterBloc(),
  child: BlocBuilder(
    builder: (context, count) {
      return ElevatedButton(
        onPressed: () => context.read().add(Increment()),
        child: Text("$count"),
      );
    },
  ),
)

// === مثال پیچیده‌تر: لیست محصولات ===
sealed class ProductsEvent {}
class LoadProducts extends ProductsEvent {}
class RefreshProducts extends ProductsEvent {}

sealed class ProductsState {}
class ProductsInitial extends ProductsState {}
class ProductsLoading extends ProductsState {}
class ProductsLoaded extends ProductsState {
  final List products;
  ProductsLoaded(this.products);
}
class ProductsError extends ProductsState {
  final String message;
  ProductsError(this.message);
}

class ProductsBloc extends Bloc {
  final ApiClient api;
  
  ProductsBloc(this.api) : super(ProductsInitial()) {
    on(_onLoad);
    on(_onRefresh);
  }
  
  Future _onLoad(LoadProducts event, Emitter emit) async {
    emit(ProductsLoading());
    try {
      final products = await api.getProducts();
      emit(ProductsLoaded(products));
    } catch (e) {
      emit(ProductsError(e.toString()));
    }
  }
  
  Future _onRefresh(RefreshProducts event, Emitter emit) async {
    if (state is ProductsLoaded) {
      // refresh بدون نمایش loading
    }
    // ...
  }
}

// در Widget
BlocBuilder(
  builder: (context, state) {
    return switch (state) {
      ProductsInitial() => const Text("شروع کنید"),
      ProductsLoading() => const CircularProgressIndicator(),
      ProductsLoaded(:final products) => ListView.builder(
        itemCount: products.length,
        itemBuilder: (_, i) => ProductTile(product: products[i]),
      ),
      ProductsError(:final message) => Text("خطا: $message"),
    };
  },
)

// BlocListener برای side effects
BlocListener(
  listener: (context, state) {
    if (state is ProductsError) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.message)),
      );
    }
  },
  child: ProductsList(),
)
    

۶.۸ GetX — All-in-one

GetX پکیجی است که state management، routing و dependency injection را با هم ارائه می‌دهد. محبوب در ایران اما در جامعه جهانی Flutter بحث‌برانگیز است.


import "package:get/get.dart";

// Controller
class CounterController extends GetxController {
  var count = 0.obs;  // observable
  
  void increment() => count++;
  void decrement() => count--;
}

// در Widget
class CounterPage extends StatelessWidget {
  final controller = Get.put(CounterController());
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Obx(() => Text("${controller.count}")),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}

// Snackbar، Dialog، Navigation با GetX
Get.snackbar("عنوان", "پیام");
Get.dialog(AlertDialog(title: Text("سلام")));
Get.to(() => const NextPage());
Get.back();
    
توصیه: اگر تازه شروع می‌کنید، Riverpod یا Bloc بهتر است. GetX با کنوانسیون‌های Flutter سازگار نیست و نگهداری کد را در پروژه‌های بزرگ سخت می‌کند.

۶.۹ مقایسه و انتخاب

روش پیچیدگی اندازه پروژه Boilerplate توصیه
setState کم خیلی کوچک هیچ state محلی
Provider کم کوچک تا متوسط متوسط پروژه‌های قدیمی
Riverpod متوسط متوسط تا بزرگ کم (با codegen) ⭐ پروژه‌های جدید
Cubit متوسط متوسط تا بزرگ کم تیم سازگار با OOP
Bloc زیاد بزرگ enterprise زیاد پروژه‌های پیچیده
GetX کم هر اندازه کم تیم‌های ایرانی (با احتیاط)
توصیه نهایی: برای پروژه جدید با Riverpod شروع کنید. اگر تیم بزرگ enterprise دارید، Bloc هم انتخاب خوبی است.

۶.۱۰ بهترین تجربیات

  1. Ephemeral state در widget بماند. setState کافی است.
  2. Business logic از UI جدا. در provider/bloc/notifier.
  3. یک روش انتخاب کنید. ترکیب نکنید.
  4. Testable بنویسید. logic بدون BuildContext.
  5. Immutable state. با copyWith.
  6. Selector/Consumer برای rebuild محدود.
  7. dispose را فراموش نکنید. یا از auto-dispose استفاده کنید.
  8. Loading، Error، Success state ها. همیشه ۳ حالت داشته باشید.

۶.۱۱ خلاصه فصل

  • setState برای state محلی
  • Lifting state up برای اشتراک محدود
  • Provider با ChangeNotifier
  • Riverpod مدرن، type-safe، توصیه شده
  • Bloc/Cubit برای enterprise
  • GetX سریع ولی بحث‌برانگیز
  • انتخاب بر اساس اندازه پروژه و تیم
در فصل بعد: شبکه و API — ارتباط با Django REST Framework، JWT، file upload.

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.