6 sätt att hantera tillstånd i Flutter-appar (med kodexempel)
Hur man hanterar tillstånd i Flutter
I den här artikeln kommer vi att utforska sex populära sätt att hantera tillstånd i Flutter appar, inklusive verkliga exempel och bästa praxis:
Hantering av tillstånd är ett av de viktigaste - och mest debatterade - ämnena inom Flutter-utveckling. Det bestämmer hur din app hanterar förändringar i data och uppdaterar gränssnittet effektivt. Flutter ger dig flera strategier för att hantera tillstånd — från enkla till högt skalbara.
Strategierna för hantering av tillstånd i Flutter är:
- 🧱
setState()— Den inbyggda, enklaste metoden - 🏗️ InheritedWidget — Flutter:s grund för spridning av tillstånd
- 🪄 Provider — Det rekommenderade lösningen för de flesta appar
- 🔒 Riverpod — Modern, kompileringssäker utveckling av Provider
- 📦 Bloc — För skalbara, företagsgradiga applikationer
- ⚡ GetX — Lättviktslösning all-i-ett

1. Användning av setState() — Grunderna
Det enklaste sättet att hantera tillstånd i Flutter är att använda den inbyggda setState()-metoden i en StatefulWidget.
Den här metoden är idealisk för lokal UI-tillstånd — där tillståndet tillhör en widget och inte behöver delas över hela appen.
Exempel: Räknare-app med 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),
),
);
}
}
Fördelar
- Mycket enkelt att implementera
- Bra för lokal eller tillfällig UI-tillstånd
- Inga externa beroenden
Nackdelar
- Skalar inte för stora appar
- Svårt att dela tillstånd mellan widgets
- Logik blandad med UI
Använd det när:
- Prototypande eller byggande av små widgets
- Hantering av isolerad UI-tillstånd (t.ex. växling av knapp, visning av modal)
2. InheritedWidget — Flutter:s grund
InheritedWidget är den lågnivåmekanism som Flutter använder för att sprida data nedåt i widget-trädet. De flesta lösningar för hantering av tillstånd (inklusive Provider) är byggda på den.
Att förstå InheritedWidget hjälper dig att förstå hur Flutter:s hantering av tillstånd fungerar under ytan.
Exempel: Tema-hanterare med InheritedWidget
import 'package:flutter/material.dart';
// Den InheritedWidget som innehåller tema-data
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);
// Metod för att få tag på närmaste AppTheme upp i widget-trädet
static AppTheme? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<AppTheme>();
}
@override
bool updateShouldNotify(AppTheme oldWidget) {
return isDarkMode != oldWidget.isDarkMode;
}
}
// Stateful-wrapper för att hantera tillståndsförändringar
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(
'Aktuellt tema: ${theme!.isDarkMode ? "Mörkt" : "Ljust"}',
style: TextStyle(fontSize: 20),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => theme.toggleTheme(),
child: Text('Växla tema'),
),
],
),
),
);
}
}
Fördelar
- Byggt in i Flutter — inga externa beroenden
- Effektiva widget-uppdateringar
- Grund för att förstå andra lösningar
- Direkt kontroll över spridningslogik
Nackdelar
- Verbos boilerplate-kod
- Kräver wrapper StatefulWidget
- Lätt att göra misstag
- Inte nybörjarvänlig
Använd det när:
- Du lär dig hur Flutter:s hantering av tillstånd fungerar internt
- Du bygger anpassade lösningar för hantering av tillstånd
- Du behöver mycket specifik kontroll över spridning av tillstånd
3. Provider — Det Flutter-rekommenderade lösningen
När din apps tillstånd behöver delas mellan flera widgets, kommer Provider till undsättning.
Provider är baserat på Inversion of Control — istället för att widgets äger tillståndet, exponerar en provider det för andra att konsumera. Flutter-teamet rekommenderar det officiellt för medelstora appar.
Installation
Lägg till detta i din pubspec.yaml:
dependencies:
provider: ^6.0.5
Exempel: Räknare-app med 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),
),
);
}
}
Fördelar
- Reaktiva och effektiva uppdateringar
- Ren separation av UI och logik
- Väl dokumenterad och community-stödd
Nackdelar
- Lite mer boilerplate än
setState() - Nästlade providers kan bli komplexa
Använd det när:
- Du behöver delat tillstånd mellan flera widgets
- Du vill ha ett reaktivt mönster utan komplexitet
- Din app växer bortom prototyp-storlek
4. Riverpod — Providers moderna utveckling
Riverpod är en komplett omskrivning av Provider som tar bort dess beroende av BuildContext och lägger till kompileringssäkerhet. Det är utformat för att lösa Providers begränsningar samtidigt som samma filosofi bibehålls.
Installation
Lägg till detta i din pubspec.yaml:
dependencies:
flutter_riverpod: ^2.4.0
Exempel: Användarprofil med Riverpod
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Modell
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: 'Gäst', 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();
});
// Beräknad provider
final greetingProvider = Provider<String>((ref) {
final profile = ref.watch(profileProvider);
return 'Hej, ${profile.name}! Du är ${profile.age} år gammal.';
});
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: 'Namn'),
onChanged: (value) {
ref.read(profileProvider.notifier).updateName(value);
},
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Ålder: ${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('-'),
),
],
),
],
),
),
);
}
}
Fördelar
- Inget beroende av BuildContext — åtkomst till tillstånd var som helst
- Kompileringssäkerhet — fånga fel innan körning
- Bättre testbarhet
- Kraftfull tillståndskomposition
- Inga minnesläckor
Nackdelar
- Annan API än traditionella Flutter-mönster
- Brantare inlärningskurva än Provider
- Mindre community (men växer snabbt)
Använd det när:
- Du startar ett nytt Flutter-projekt
- Du vill ha typ-säkerhet och kompileringsgarantier
- Komplexa tillståndsberoenden och kompositioner
- Arbeta med medelstora-till-stora applikationer
5. BLoC (Business Logic Component) — För företagsappar
BLoC mönstret är en mer avancerad arkitektur som helt separerar affärslogiken från gränssnittet med hjälp av strömmmar.
Det är idealiskt för stora eller företagsappar, där förutsägbara och testbara tillståndsövergångar är avgörande.
Installation
Lägg till denna beroende i ditt projekt:
dependencies:
flutter_bloc: ^9.0.0
Exempel: Räknare-app med BLoC
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// Händelser
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 Räknare')),
body: Center(
child: BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Text('Räkning: $count', style: TextStyle(fontSize: 24));
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Icon(Icons.add),
),
);
}
}
Fördelar
- Skalbart och underhållbart för komplexa appar
- Klar separation av lager
- Lätt att enhetstesta
Nackdelar
- Mer boilerplate
- Inlärningskurvan är brantare
Använd det när:
- Bygga företags- eller långsiktiga projekt
- Du behöver förutsägbar och testbar logik
- Flera utvecklare arbetar på olika appmoduler
6. GetX — Den lättviktslösa all-i-ett-lösningen
GetX är en minimalistisk men kraftfull lösning för hantering av tillstånd som också inkluderar routing, beroendeinjektion och verktyg. Det är känt för att ha minst boilerplate av alla lösningar.
Installation
Lägg till detta i din pubspec.yaml:
dependencies:
get: ^4.6.5
Exempel: Inköpslista med GetX
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// Kontroller
class ShoppingController extends GetxController {
// Observerbar lista
var items = <String>[].obs;
// Observerbar räknare
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 Inköpslista',
home: ShoppingScreen(),
);
}
}
class ShoppingScreen extends StatelessWidget {
// Initiera kontroller
final ShoppingController controller = Get.put(ShoppingController());
final TextEditingController textController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GetX Inköpslista'),
actions: [
Obx(() => Padding(
padding: EdgeInsets.all(16),
child: Center(
child: Text(
'Antal: ${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: 'Ange varanamn',
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('Inga varor i din lista'),
);
}
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: 'Rensa Lista',
middleText: 'Är du säker på att du vill rensa alla varor?',
textConfirm: 'Ja',
textCancel: 'Nej',
onConfirm: () {
controller.clearAll();
Get.back();
},
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
minimumSize: Size(double.infinity, 50),
),
child: Text('Rensa Allt'),
),
),
],
),
);
}
}
Fördelar
- Minimal boilerplate — snabbast utveckling
- All-i-ett-lösning (tillstånd, routing, DI, meddelanden, dialoger)
- Inget BuildContext behövs
- Mycket lättviktig och snabb
- Lätt att lära
Nackdelar
- Mindre “Flutter-lik” jämfört med Provider/BLoC
- Kan leda till stark koppling om man inte är försiktig
- Mindre ekosystem än Provider
- Vissa utvecklare tycker det är “för magiskt”
Använd det när:
- Snabb prototyp eller MVP-utveckling
- Små till medelstora appar
- Du vill ha minimal boilerplate
- Teamet föredrar enkelhet framför strikt arkitektur
Val av rätt lösning för tillståndshantering
| Lösning | Komplexitet | Boilerplate | Inlärningskurva | Bäst för |
|---|---|---|---|---|
🧱 setState() |
Låg | Minimal | Lätt | Enkel lokal tillstånd, prototyper |
🏗️ InheritedWidget |
Medel | Hög | Medel | Att lära sig Flutter-interner, anpassade lösningar |
🪄 Provider |
Låg-Medel | Låg | Lätt | De flesta appar, delat tillstånd |
🔒 Riverpod |
Medel | Låg-Medel | Medel | Moderna appar, typ säkerhet |
📦 BLoC |
Hög | Hög | Brant | Företagsappar, komplex affärslogik |
⚡ GetX |
Låg | Minimal | Lätt | Snabb utveckling, MVPs |
Snabb beslutsguide:
| App komplexitet | Rekommenderad metod | Exempelanvändning |
|---|---|---|
| 🪶 Enkel | setState() eller GetX |
Knappväxling, animationer, små widgetar |
| ⚖️ Medel | Provider eller Riverpod |
Delade teman, användarinställningar, datacaching |
| 🏗️ Komplex | BLoC eller Riverpod |
E-handel, chattappar, finansiella instrumentpaneler |
Slutsatser
Det finns ingen allmänt passande lösning för tillståndshantering i Flutter.
Här är ett praktiskt tillvägagångssätt:
- Börja enkelt med
setState()för enkla widgetar och prototyper - Lär dig grunderna med InheritedWidget för att förstå hur Flutter fungerar internt
- Gå vidare till Provider när din app växer och behöver delat tillstånd
- Överväg Riverpod för nya projekt där typ säkerhet och moderna mönster är viktiga
- Anamma BLoC för stora företagsappar med komplex affärslogik
- Prova GetX när snabb utveckling och minimal boilerplate är prioriterat
Viktiga slutsatser:
✅ Överdriv inte - använd setState() tills du behöver mer
✅ Provider och Riverpod är utmärkta val för de flesta applikationer
✅ BLoC lyser i stora team och komplexa appar
✅ GetX är bra för hastighet, men var medveten om koppling
✅ Att förstå InheritedWidget hjälper dig att behärska vilken lösning som helst
Nyckeln är att balansera enkelhet, skalbarhet och underhållbarhet - och välja rätt verktyg för dina specifika behov, teamets expertis och projektkrav.
Länkar till Flutter State Management-bibliotek
- Flutter officiell dokumentation om tillståndshantering
- Provider-paket
- Riverpod-dokumentation
- BLoC-bibliotek
- GetX-dokumentation