State Management using Provider in flutter

GEC Rajkot 2026.
Goal: Learn modern Flutter state management with the provider package by building the smallest possible app that demonstrates real-world patterns: wiring a provider, reading state, mutating state, avoiding unnecessary rebuilds, and writing testable logic. All code is copy-paste ready.
Prerequisites :
Flutter SDK installed (stable channel).
Basic Dart + Flutter familiarity (
StatelessWidget,StatefulWidget,MaterialApp).Add
providertopubspec.yaml:
dependencies:
flutter:
sdk: flutter
provider: ^6.0.5
What you’ll build
A minimal app with:
A
Countermodel (as aChangeNotifier).A
HomeScreenthat displays the counter and provides increment/decrement/reset actions.ChangeNotifierProviderwired at app root.
This shows the most common state-management scenarios simply and correctly.
File 1 — lib/models/counter.dart
Purpose: Small, testable model that owns the state and business logic.
import 'package:flutter/foundation.dart';
class Counter extends ChangeNotifier {
int _value = 0;
int get value => _value;
void increment() {
_value++;
notifyListeners();//rebuild
}
void decrement() {
if (_value > 0) {
_value--;
notifyListeners();//rebuild
}
}
void reset() {
_value = 0;
notifyListeners();//rebuild
}
}
Explanation :Counter holds the integer _value. Public getter value returns it. Methods change the value and call notifyListeners() so widgets that watch this model rebuild. Business rules (e.g., preventing negative numbers) live in the model.
Why this is good: keeps UI code free of business logic and makes unit testing trivial.
File 2 — lib/main.dart
Purpose: App entry point and provider wiring.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/counter.dart';
import 'screens/home.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => Counter(),
child: MaterialApp(
title: 'Provider Counter',
home: const HomeScreen(),
),
);
}
}
Explanation (simple):ChangeNotifierProvider creates and exposes a single Counter instance to the widget tree. Any widget under MyApp can read or watch that Counter.
Best practice: Use MultiProvider when you have multiple providers (theme, auth, repositories).
File 3 — lib/screens/home.dart
Purpose: UI that shows the counter and triggers actions.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../Models/counter.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
final counter = context.watch<Counter>(); <----
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Center(
child: Column(
children: [
Text(
'Value: ${counter.getValue()}',
style: const TextStyle(fontSize: 30),
),
FloatingActionButton(
onPressed: () {
context.read<Counter>().decrement(); <------
},
child: const Icon(CupertinoIcons.minus_circle_fill),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<Counter>().increment(), <----
child: const Icon(CupertinoIcons.plus_app_fill),
),
);
}
}
Explanation (simple):
final counter = context.watch<Counter>();
It gets the Counter object from Provider
It also subscribes this widget to updates
Whenever
notifyListeners()is called insideCounter,
→ the entireHomeScreenrebuilds
context.read<Counter>().increment()
This line says:
“Get the
Counterobject from Provider and call itsincrement()method — without listening for changes.”
It is used when:
You only want to perform an action
You do not want this widget to rebuild
You are inside a button click, gesture, or event

Multi Provider :
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:x/Models/decrementer.dart';
import 'package:x/Models/incrementer.dart';
import 'screens/home.dart';
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Incrementer()),
ChangeNotifierProvider(create: (_) => Decrementer()),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(home: HomeScreen(), debugShowCheckedModeBanner: false);
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:x/Models/decrementer.dart';
import 'package:x/Models/incrementer.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
final incrementer = context.watch<Incrementer>();
final decrementer = context.watch<Decrementer>();
return Scaffold(
body: Column(
children: [
Row(
children: [
Text('Value is ${incrementer.getValue()}'),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => context.read<Incrementer>().increment(),
child: Text('Increment'),
),
],
),
const SizedBox(height: 10),
Row(
children: [
Text('Value is ${decrementer.getValue()}'),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => context.read<Decrementer>().decrement(),
child: Text('Decrement'),
),
],
),
],
),
);
}
}
import 'package:flutter/material.dart';
class Decrementer extends ChangeNotifier {
int _value = 0;
int getValue() {
return _value;
}
void decrement() {
_value--;
notifyListeners();
}
}
import 'package:flutter/material.dart';
class Incrementer extends ChangeNotifier {
int _value = 0;
int getValue() {
return _value;
}
void increment() {
_value++;
notifyListeners();
}
}

Why provider and this pattern?
Simple & idiomatic:
provideris official and lightweight.Separation of concerns: Models hold logic, widgets handle UI.
