0Pricing
Flutter Mobile Development · Lesson

Provider Package Basics

Learn to use the Provider package, a popular Flutter solution, for simple and scalable state management across your application.

Provider Package Basics is a free Flutter Mobile Development lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Flutter Mobile Development learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What is Provider?

The Provider package is a popular and simple solution for state management in Flutter. It's built on top of Flutter's own InheritedWidget, making it efficient and easy to understand.

It helps you share data and state across your widget tree without complex callbacks or prop drilling.

The Data Holder: ChangeNotifier

At the heart of Provider is the ChangeNotifier class. You extend this class for any object that holds state you want to share.

When your state changes, you call notifyListeners() to tell all listening widgets to rebuild.

import 'package:flutter/material.dart';

// 1. Extend ChangeNotifier
class MyCounter extends ChangeNotifier {
  int _count = 0; // Private state variable

  // Getter to access the count
  int get count => _count;

  // Method to modify state and notify listeners
  void increment() {
    _count++;
    notifyListeners(); // Important! Triggers UI updates
  }
}

Making State Available

To make your ChangeNotifier instance available to widgets, you use a ChangeNotifierProvider. You wrap a part of your widget tree (usually your whole app) with it.

The create callback provides an instance of your state object.

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

// Our state class from before
class MyCounter extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() {
    _count++;
    notifyListeners();
  }
}

void main() {
  runApp(
    // Wrap the app with ChangeNotifierProvider
    ChangeNotifierProvider(
      create: (context) => MyCounter(), // Create an instance
      child: MyApp(), // The widget tree that can access MyCounter
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Provider Setup')),
        body: Center(
          child: Text(
            'MyCounter is now provided!',
            style: TextStyle(fontSize: 24),
          ),
        ),
      ),
    );
  }
}

Listening for UI Rebuilds

The most common way for a widget to listen to state changes and rebuild its UI is using context.watch(). This method makes the widget dependent on the provided state.

When notifyListeners() is called, any widget using watch will rebuild.

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

class MyCounter extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() {
    _count++;
    notifyListeners();
  }
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 1. Use context.watch<MyCounter>() to get the current state
    //    This widget will rebuild when MyCounter changes.
    final counter = context.watch<MyCounter>();

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Watch for Changes')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                'Count: ${counter.count}', // Display the count
                style: TextStyle(fontSize: 32),
              ),
              SizedBox(height: 20),
              // Button to increment (action will be added next)
              ElevatedButton(
                onPressed: () {
                  // How to trigger increment? See next scene!
                },
                child: Text('Increment (Not yet connected)'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Actions without Rebuilds

Sometimes you need to access state methods (like increment()) but don't want the widget to rebuild. For this, use context.read().

It provides a one-time access to the state object and does not establish a listening relationship.

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

class MyCounter extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() {
    _count++;
    notifyListeners();
  }
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = context.watch<MyCounter>(); // Listen for UI updates

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Read for Actions')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                'Count: ${counter.count}',
                style: TextStyle(fontSize: 32),
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  // 1. Use context.read<MyCounter>() to get the instance
                  // 2. Call the increment method
                  context.read<MyCounter>().increment();
                },
                child: Text('Increment Counter'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Alternative for One-Time Access

Before context.read(), the common way to access state without listening was Provider.of(context, listen: false).

It works exactly like context.read(), but read is often preferred for its conciseness. Both are suitable for event handlers like onPressed.

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

class MyCounter extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() {
    _count++;
    notifyListeners();
  }
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = context.watch<MyCounter>(); // UI listens

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Provider.of(listen: false)')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                'Count: ${counter.count}',
                style: TextStyle(fontSize: 32),
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  // Use Provider.of with listen: false for one-time access
                  Provider.of<MyCounter>(context, listen: false).increment();
                },
                child: Text('Increment Counter (using Provider.of)'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Fine-Grained Rebuilds with Consumer

The Consumer widget is another way to listen to state changes. Unlike context.watch(), which rebuilds the entire widget, Consumer only rebuilds its direct child (the builder function).

This can be useful for performance if only a small part of a large widget needs to react to state changes.

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

class MyCounter extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() {
    _count++;
    notifyListeners();
  }
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Consumer Widget')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                'This text does NOT rebuild.',
                style: TextStyle(fontSize: 20, color: Colors.grey),
              ),
              SizedBox(height: 20),
              // Only the Text widget inside Consumer rebuilds
              Consumer<MyCounter>(
                builder: (context, counter, child) {
                  print('Consumer rebuilt!'); // See this in console
                  return Text(
                    'Count: ${counter.count}',
                    style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
                  );
                },
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            context.read<MyCounter>().increment();
          },
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

Providing Multiple States

For apps with multiple independent state objects, wrapping your app with many ChangeNotifierProviders can get messy. MultiProvider simplifies this.

It takes a list of providers, making your widget tree cleaner and easier to manage.

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

class MyCounter extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() {
    _count++;
    notifyListeners();
  }
}

class MyThemeSettings extends ChangeNotifier {
  bool _isDark = false;
  bool get isDark => _isDark;
  void toggleTheme() {
    _isDark = !_isDark;
    notifyListeners();
  }
}

void main() {
  runApp(
    MultiProvider( // Use MultiProvider for multiple states
      providers: [
        ChangeNotifierProvider(create: (context) => MyCounter()),
        ChangeNotifierProvider(create: (context) => MyThemeSettings()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Access both states using watch
    final counter = context.watch<MyCounter>();
    final theme = context.watch<MyThemeSettings>();

    return MaterialApp(
      theme: theme.isDark ? ThemeData.dark() : ThemeData.light(),
      home: Scaffold(
        appBar: AppBar(title: Text('MultiProvider Demo')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Count: ${counter.count}', style: TextStyle(fontSize: 28)),
              SizedBox(height: 20),
              Text('Dark Mode: ${theme.isDark ? "On" : "Off"}', style: TextStyle(fontSize: 28)),
              SizedBox(height: 30),
              ElevatedButton(
                onPressed: () => context.read<MyThemeSettings>().toggleTheme(),
                child: Text('Toggle Theme'),
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => context.read<MyCounter>().increment(),
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

Provider Quick Check

Which of the following is the primary purpose of context.read() in the Provider package?

Provider Basics Recap

You've learned the essentials of the Provider package!

  • ChangeNotifier holds your application state.
  • ChangeNotifierProvider makes that state available.
  • context.watch() listens for state changes and rebuilds UI.
  • context.read() accesses state for actions without rebuilding.
  • Provider.of(listen: false) is an older alternative to read.
  • Consumer allows for fine-grained UI rebuilds.
  • MultiProvider helps manage multiple state objects cleanly.

Provider is a powerful and flexible tool for managing state in your Flutter apps!

Frequently asked questions

Is the “Provider Package Basics” lesson free?

Yes — the full text of “Provider Package Basics” is free to read here on the web, and the Flutter Mobile Development course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Flutter Mobile Development course, upgrade to CoddyKit PRO.

What will I learn in “Provider Package Basics”?

Learn to use the Provider package, a popular Flutter solution, for simple and scalable state management across your application. You practise Flutter Mobile Development with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Flutter Mobile Development?

No prior experience is required. Flutter Mobile Development on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Provider Package Basics” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Flutter Mobile Development lesson?

Yes. Every Flutter Mobile Development lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. setState & InheritedWidget
  2. Provider Package Basics
  3. Riverpod for State
  4. BLoC Pattern with Streams
← Back to Flutter Mobile Development