6 Cara Mengelola State dalam Aplikasi Flutter (Dengan Contoh Kode)

Cara mengelola state di Flutter

Konten Halaman

Dalam artikel ini, kita akan menjelajahi enam cara populer untuk mengelola state di Flutter aplikasi, termasuk contoh nyata dan praktik terbaik:

Manajemen state adalah salah satu topik yang paling penting - dan dibatasi - dalam pengembangan Flutter. Ini menentukan bagaimana aplikasi Anda menangani perubahan data dan memperbarui UI secara efisien. Flutter memberi Anda beberapa strategi untuk mengelola state — mulai dari yang sederhana hingga sangat skalabel.

Strategi manajemen state di Flutter adalah:

  1. 🧱 setState() — Pendekatan bawa-in, paling sederhana
  2. 🏗️ InheritedWidget — Fondasi Flutter untuk propagasi state
  3. 🪄 Provider — Solusi yang direkomendasikan untuk sebagian besar aplikasi
  4. 🔒 Riverpod — Evolusi modern, aman saat kompilasi dari Provider
  5. 📦 Bloc — Untuk aplikasi yang skalabel, berbasis perusahaan
  6. GetX — Solusi ringkas all-in-one

flutter troubles mega tracktor


1. Menggunakan setState() — Dasar-dasarnya

Cara paling sederhana untuk mengelola state di Flutter adalah dengan menggunakan metode bawa-in setState() dalam StatefulWidget.

Pendekatan ini ideal untuk state UI lokal — di mana state milik satu widget dan tidak perlu dibagikan di seluruh aplikasi.

Contoh: Aplikasi Counter dengan setState()

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: CounterScreen());
  }
}

class CounterScreen extends StatefulWidget {
  @override
  _CounterScreenState createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('setState Counter')),
      body: Center(
        child: Text('Count: $_count', style: TextStyle(fontSize: 24)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

Kelebihan

  • Sangat mudah diimplementasikan
  • Cocok untuk state UI lokal atau sementara
  • Tidak ada ketergantungan eksternal

Kekurangan

  • Tidak skalabel untuk aplikasi besar
  • Sulit untuk berbagi state antar widget
  • Logika tercampur dengan UI

Gunakan ketika:

  • Membuat prototipe atau widget kecil
  • Mengelola state UI terisolasi (misalnya, mengaktifkan tombol, menampilkan modal)

2. InheritedWidget — Fondasi Flutter

InheritedWidget adalah mekanisme tingkat rendah yang digunakan Flutter untuk menyebar data ke bawah pohon widget. Sebagian besar solusi manajemen state (termasuk Provider) dibangun di atasnya.

Memahami InheritedWidget membantu Anda memahami bagaimana manajemen state Flutter bekerja di bawah hood.

Contoh: Manajer Tema dengan InheritedWidget

import 'package:flutter/material.dart';

// InheritedWidget yang menyimpan data tema
class AppTheme extends InheritedWidget {
  final bool isDarkMode;
  final Function toggleTheme;

  const AppTheme({
    Key? key,
    required this.isDarkMode,
    required this.toggleTheme,
    required Widget child,
  }) : super(key: key, child: child);

  // Metode akses untuk mendapatkan AppTheme terdekat di pohon widget
  static AppTheme? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<AppTheme>();
  }

  @override
  bool updateShouldNotify(AppTheme oldWidget) {
    return isDarkMode != oldWidget.isDarkMode;
  }
}

// Wrapper stateful untuk mengelola perubahan state
class ThemeManager extends StatefulWidget {
  final Widget child;

  const ThemeManager({Key? key, required this.child}) : super(key: key);

  @override
  _ThemeManagerState createState() => _ThemeManagerState();
}

class _ThemeManagerState extends State<ThemeManager> {
  bool _isDarkMode = false;

  void _toggleTheme() {
    setState(() {
      _isDarkMode = !_isDarkMode;
    });
  }

  @override
  Widget build(BuildContext context) {
    return AppTheme(
      isDarkMode: _isDarkMode,
      toggleTheme: _toggleTheme,
      child: widget.child,
    );
  }
}

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ThemeManager(
      child: Builder(
        builder: (context) {
          final theme = AppTheme.of(context);
          return MaterialApp(
            theme: theme!.isDarkMode ? ThemeData.dark() : ThemeData.light(),
            home: HomeScreen(),
          );
        },
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final theme = AppTheme.of(context);
    
    return Scaffold(
      appBar: AppBar(title: Text('InheritedWidget Theme')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Current Theme: ${theme!.isDarkMode ? "Dark" : "Light"}',
              style: TextStyle(fontSize: 20),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => theme.toggleTheme(),
              child: Text('Toggle Theme'),
            ),
          ],
        ),
      ),
    );
  }
}

Kelebihan

  • Terintegrasi dengan Flutter — tidak ada ketergantungan eksternal
  • Perbaruan widget yang efisien
  • Fondasi untuk memahami solusi lain
  • Kontrol langsung atas logika propagasi

Kekurangan

  • Kode boilerplate yang verbose
  • Membutuhkan wrapper StatefulWidget
  • Mudah membuat kesalahan
  • Tidak ramah bagi pemula

Gunakan ketika:

  • Mempelajari bagaimana manajemen state Flutter bekerja secara internal
  • Membangun solusi manajemen state kustom
  • Anda membutuhkan kontrol sangat spesifik atas propagasi state

3. Provider — Solusi yang Direkomendasikan oleh Flutter

Ketika state aplikasi Anda perlu dibagikan antar beberapa widget, Provider datang untuk menyelamatkan.

Provider berbasis pada Inversion of Control — alih-alih widget memiliki state, provider mengeksposkannya untuk dikonsumsi oleh yang lain. Tim Flutter secara resmi merekomendasikannya untuk aplikasi berukuran sedang.

Pengaturan

Tambahkan ini ke pubspec.yaml Anda:

dependencies:
  provider: ^6.0.5

Contoh: Aplikasi Counter dengan Provider

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CounterModel(),
      child: MyApp(),
    ),
  );
}

class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: CounterScreen());
  }
}

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = context.watch<CounterModel>();
    return Scaffold(
      appBar: AppBar(title: Text('Provider Counter')),
      body: Center(
        child: Text('Count: ${counter.count}', style: TextStyle(fontSize: 24)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: context.read<CounterModel>().increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

Kelebihan

  • Perbaruan reaktif dan efisien
  • Pemisahan bersih antara UI dan logika
  • Dokumentasi yang baik dan didukung komunitas

Kekurangan

  • Sedikit lebih banyak boilerplate dibandingkan setState()
  • Provider bersarang bisa menjadi kompleks

Gunakan ketika:

  • Anda membutuhkan state yang dibagikan antar beberapa widget
  • Anda ingin pola reaktif tanpa kompleksitas
  • Aplikasi Anda tumbuh di luar ukuran prototipe

4. Riverpod — Evolusi Modern Provider

Riverpod adalah penulisan ulang lengkap dari Provider yang menghilangkan ketergantungan pada BuildContext dan menambahkan keamanan saat kompilasi. Dirancang untuk menyelesaikan keterbatasan Provider sambil mempertahankan filosofi yang sama.

Pengaturan

Tambahkan ini ke pubspec.yaml Anda:

dependencies:
  flutter_riverpod: ^2.4.0

Contoh: Profil Pengguna dengan Riverpod

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// Model
class UserProfile {
  final String name;
  final int age;
  
  UserProfile({required this.name, required this.age});
  
  UserProfile copyWith({String? name, int? age}) {
    return UserProfile(
      name: name ?? this.name,
      age: age ?? this.age,
    );
  }
}

// State Notifier
class ProfileNotifier extends StateNotifier<UserProfile> {
  ProfileNotifier() : super(UserProfile(name: 'Guest', age: 0));

  void updateName(String name) {
    state = state.copyWith(name: name);
  }

  void updateAge(int age) {
    state = state.copyWith(age: age);
  }
}

// Provider
final profileProvider = StateNotifierProvider<ProfileNotifier, UserProfile>((ref) {
  return ProfileNotifier();
});

// Provider yang dihitung
final greetingProvider = Provider<String>((ref) {
  final profile = ref.watch(profileProvider);
  return 'Hello, ${profile.name}! Anda berusia ${profile.age} tahun.';
});

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: ProfileScreen());
  }
}

class ProfileScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final profile = ref.watch(profileProvider);
    final greeting = ref.watch(greetingProvider);

    return Scaffold(
      appBar: AppBar(title: Text('Riverpod Profil')),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(greeting, style: TextStyle(fontSize: 20)),
            SizedBox(height: 30),
            TextField(
              decoration: InputDecoration(labelText: 'Nama'),
              onChanged: (value) {
                ref.read(profileProvider.notifier).updateName(value);
              },
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Usia: ${profile.age}', style: TextStyle(fontSize: 18)),
                SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    ref.read(profileProvider.notifier).updateAge(profile.age + 1);
                  },
                  child: Text('+'),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () {
                    if (profile.age > 0) {
                      ref.read(profileProvider.notifier).updateAge(profile.age - 1);
                    }
                  },
                  child: Text('-'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Kelebihan

  • Tidak bergantung pada BuildContext — akses state di mana saja
  • Keamanan saat kompilasi — tangkap kesalahan sebelum runtime
  • Pengujian yang lebih baik
  • Komposisi state yang kuat
  • Tidak ada kebocoran memori

Kekurangan

  • API berbeda dari pola tradisional Flutter
  • Kurva pembelajaran lebih curam dibandingkan Provider
  • Komunitas lebih kecil (namun berkembang pesat)

Gunakan ketika:

  • Memulai proyek Flutter baru
  • Anda ingin keamanan tipe dan jaminan saat kompilasi
  • Ketergantungan state yang kompleks dan komposisi
  • Bekerja pada aplikasi berukuran sedang hingga besar

5. BLoC (Business Logic Component) — Untuk Aplikasi Perusahaan

BLoC pattern adalah arsitektur yang lebih maju yang memisahkan sepenuhnya logika bisnis dari UI menggunakan streams.

Ini ideal untuk aplikasi berukuran besar atau perusahaan, di mana transisi state yang dapat diprediksi dan dapat diuji sangat penting.

Pengaturan

Tambahkan dependensi ini ke proyek Anda:

dependencies:
  flutter_bloc: ^9.0.0

Contoh: Aplikasi Counter dengan BLoC

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// Events
abstract class CounterEvent {}
class Increment extends CounterEvent {}

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterBloc(),
      child: MaterialApp(home: CounterScreen()),
    );
  }
}

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Bloc Counter')),
      body: Center(
        child: BlocBuilder<CounterBloc, int>(
          builder: (context, count) {
            return Text('Count: $count', style: TextStyle(fontSize: 24));
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterBloc>().add(Increment()),
        child: Icon(Icons.add),
      ),
    );
  }
}

Kelebihan

  • Skalabel dan mudah dipelihara untuk aplikasi kompleks
  • Pemisahan lapisan yang jelas
  • Mudah diuji secara unit

Kekurangan

  • Lebih banyak boilerplate
  • Kurva pembelajaran lebih curam

Gunakan ketika:

  • Membangun aplikasi perusahaan atau proyek jangka panjang
  • Anda membutuhkan logika yang dapat diprediksi dan dapat diuji
  • Banyak pengembang bekerja pada modul aplikasi yang berbeda

6. GetX — Solusi Ringkas All-in-One

GetX adalah solusi manajemen state yang minimalis namun kuat yang juga mencakup routing, injeksi ketergantungan, dan utilitas. Dikenal karena memiliki boilerplate terkecil dari semua solusi.

Pengaturan

Tambahkan ini ke pubspec.yaml Anda:

dependencies:
  get: ^4.6.5

Contoh: Daftar Belanja dengan GetX

import 'package:flutter/material.dart';
import 'package:get/get.dart';

// Controller
class ShoppingController extends GetxController {
  // Daftar yang dapat diobservasi
  var items = <String>[].obs;
  
  // Penghitung yang dapat diobservasi
  var totalItems = 0.obs;

  void addItem(String item) {
    if (item.isNotEmpty) {
      items.add(item);
      totalItems.value = items.length;
    }
  }

  void removeItem(int index) {
    items.removeAt(index);
    totalItems.value = items.length;
  }

  void clearAll() {
    items.clear();
    totalItems.value = 0;
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'GetX Shopping List',
      home: ShoppingScreen(),
    );
  }
}

class ShoppingScreen extends StatelessWidget {
  // Inisialisasi controller
  final ShoppingController controller = Get.put(ShoppingController());
  final TextEditingController textController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GetX Shopping List'),
        actions: [
          Obx(() => Padding(
                padding: EdgeInsets.all(16),
                child: Center(
                  child: Text(
                    'Items: ${controller.totalItems}',
                    style: TextStyle(fontSize: 18),
                  ),
                ),
              )),
        ],
      ),
      body: Column(
        children: [
          Padding(
            padding: EdgeInsets.all(16),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: textController,
                    decoration: InputDecoration(
                      hintText: 'Enter item name',
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () {
                    controller.addItem(textController.text);
                    textController.clear();
                  },
                  child: Icon(Icons.add),
                ),
              ],
            ),
          ),
          Expanded(
            child: Obx(() {
              if (controller.items.isEmpty) {
                return Center(
                  child: Text('No items in your list'),
                );
              }
              
              return ListView.builder(
                itemCount: controller.items.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    leading: CircleAvatar(child: Text('${index + 1}')),
                    title: Text(controller.items[index]),
                    trailing: IconButton(
                      icon: Icon(Icons.delete, color: Colors.red),
                      onPressed: () => controller.removeItem(index),
                    ),
                  );
                },
              );
            }),
          ),
          if (controller.items.isNotEmpty)
            Padding(
              padding: EdgeInsets.all(16),
              child: ElevatedButton(
                onPressed: () {
                  Get.defaultDialog(
                    title: 'Clear List',
                    middleText: 'Are you sure you want to clear all items?',
                    textConfirm: 'Yes',
                    textCancel: 'No',
                    onConfirm: () {
                      controller.clearAll();
                      Get.back();
                    },
                  );
                },
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.red,
                  minimumSize: Size(double.infinity, 50),
                ),
                child: Text('Clear All'),
              ),
            ),
        ],
      ),
    );
  }
}

Kelebihan

  • Boilerplate minimal — pengembangan tercepat
  • Solusi all-in-one (state, routing, DI, snackbars, dialog)
  • Tidak membutuhkan BuildContext
  • Sangat ringkas dan cepat
  • Mudah dipelajari

Kekurangan

  • Kurang “Flutter-like” dibandingkan Provider/BLoC
  • Bisa menyebabkan ketergantungan ketat jika tidak hati-hati
  • Ekosistem lebih kecil dibandingkan Provider
  • Beberapa pengembang menemukannya “terlalu ajaib”

Gunakan ketika:

  • Pengembangan prototipe cepat atau MVP
  • Aplikasi kecil hingga sedang
  • Anda ingin boilerplate minimal
  • Tim lebih memilih kesederhanaan daripada arsitektur ketat

Memilih Solusi Manajemen State yang Tepat

Solusi Tingkat Kesulitan Boilerplate Kurva Pembelajaran Terbaik Untuk
🧱 setState() Rendah Minimal Mudah State lokal sederhana, prototipe
🏗️ InheritedWidget Sedang Tinggi Sedang Mempelajari internal Flutter, solusi kustom
🪄 Provider Rendah-Sedang Rendah Mudah Sebagian besar aplikasi, state yang dibagikan
🔒 Riverpod Sedang Rendah-Sedang Sedang Aplikasi modern, keamanan tipe
📦 BLoC Tinggi Tinggi Curam Aplikasi perusahaan, logika bisnis kompleks
GetX Rendah Minimal Mudah Pengembangan cepat, MVP

Panduan Keputusan Cepat:

Tingkat Kompleksitas Aplikasi Pendekatan yang Direkomendasikan Contoh Kasus Penggunaan
🪶 Sederhana setState() atau GetX Mengaktifkan tombol, animasi, widget kecil
⚖️ Sedang Provider atau Riverpod Tema yang dibagikan, pengaturan pengguna, penyimpanan data
🏗️ Kompleks BLoC atau Riverpod E-commerce, aplikasi chat, dashboard keuangan

Pikiran Akhir

Tidak ada pendekatan satu ukuran cocok untuk semua untuk manajemen state di Flutter.

Berikut pendekatan praktis:

  • Mulai dari yang sederhana dengan setState() untuk widget dan prototipe sederhana
  • Pelajari dasar-dasarnya dengan InheritedWidget untuk memahami bagaimana Flutter bekerja secara internal
  • Naik ke Provider saat aplikasi Anda berkembang dan membutuhkan state yang dibagikan
  • Pertimbangkan Riverpod untuk proyek baru di mana keamanan tipe dan pola modern penting
  • Adopsi BLoC untuk aplikasi perusahaan besar dengan logika bisnis kompleks
  • Coba GetX ketika kecepatan pengembangan dan boilerplate minimal menjadi prioritas

Poin Penting:

✅ Jangan terlalu memperkomplekskan — gunakan setState() sampai Anda membutuhkan lebih ✅ Provider dan Riverpod adalah pilihan yang sangat baik untuk sebagian besar aplikasi ✅ BLoC unggul dalam tim besar dan aplikasi kompleks ✅ GetX sangat baik untuk kecepatan, tetapi waspadai ketergantungan ✅ Memahami InheritedWidget membantu Anda menguasai solusi apa pun

Kunci adalah untuk menyeimbangkan kesederhanaan, skalabilitas, dan pemeliharaan — dan memilih alat yang tepat untuk kebutuhan spesifik Anda, keahlian tim, dan persyaratan proyek.

Tautan Perpustakaan Manajemen State Flutter

Tautan Lain yang Berguna