Skip to main content

Command Palette

Search for a command to run...

State Management using Provider in flutter

Published
4 min read
State Management using Provider in flutter
P

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 provider to pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.5

What you’ll build

A minimal app with:

  • A Counter model (as a ChangeNotifier).

  • A HomeScreen that displays the counter and provides increment/decrement/reset actions.

  • ChangeNotifierProvider wired 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 inside Counter,
    the entire HomeScreen rebuilds

context.read<Counter>().increment()

This line says:

“Get the Counter object from Provider and call its increment() 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


Animated image of a digital counter application showing a simple interface with a counter displaying "Value: 0". There are two buttons with minus and plus symbols. The value increases or decreases when the respective button is clicked.


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();
  }
}

A small window with "Value is 0" displayed twice, and two buttons labeled "Increment" and "Decrement." The buttons are clicked, changing the values to 1 and -1. The window is on a dark background.


Why provider and this pattern?

  • Simple & idiomatic: provider is official and lightweight.

  • Separation of concerns: Models hold logic, widgets handle UI.