When to Use setState, Provider, and Consumer in Flutter

BackerLeader posted 2 min read

Imagine you’re building a music player app
You have:

  • A Play button on the player screen

  • A Mini Player at the bottom of the home screen

  • A Playlist screen showing all songs

Now… Flutter gives you multiple ways to manage data and update the UI.
Three of the most common are:

setState — The “I just need to refresh this widget” approach

Think of setState as saying:

“Hey Flutter, just repaint this small part of the UI because something here changed.”

When to use:

  • Simple, temporary state

  • Widget-specific changes

  • Doesn’t need to be shared across the app

Music App Example:
Updating the Play/Pause button icon when the user taps it.

class PlayButton extends StatefulWidget {
  @override
  _PlayButtonState createState() => _PlayButtonState();
}

class _PlayButtonState extends State<PlayButton> {
  bool isPlaying = false;

  void togglePlay() {
    setState(() {
      isPlaying = !isPlaying; // just flip the icon state
    });
  }

  @override
  Widget build(BuildContext context) {
    return IconButton(
      icon: Icon(isPlaying ? Icons.pause : Icons.play_arrow),
      onPressed: togglePlay,
    );
  }
}

Here, only the Play button changes — no need to bother the rest of the app.

Provider — The “shared state manager”

Think of Provider as:

“I have data that multiple widgets need to know about, even if they’re far apart.”

When to use:

  • App-wide or screen-wide shared data

  • State needs to survive across multiple widgets/screens

  • Perfect for your music queue, playback status, user preferences

Music App Example:
Keeping track of the currently playing song so both Mini Player and Full Player Screen update together.

class MusicProvider extends ChangeNotifier {
  String? currentSong;

  void playSong(String song) {
    currentSong = song;
    notifyListeners(); // tell all listening widgets to rebuild
  }
}

// Provide at top level
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => MusicProvider(),
      child: MyApp(),
    ),
  );
}

Now you can read this currentSong anywhere in your app — that’s the magic.

Consumer — The “I only want to rebuild this tiny part” trick

Think of Consumer as:

“I’ll listen to the Provider’s changes, but only rebuild this small widget, not the whole screen.”

When to use:

  • Optimize performance (avoid rebuilding the entire widget tree)

  • Listen to changes in one spot only

Music App Example:
Updating just the song title in the mini player without reloading the play button or progress bar unnecessarily.

class MiniPlayer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey[900],
      child: Row(
        children: [
          Consumer<MusicProvider>(
            builder: (context, music, _) {
              return Text(
                music.currentSong ?? "No song playing",
                style: TextStyle(color: Colors.white),
              );
            },
          ),
          Icon(Icons.play_arrow, color: Colors.white),
        ],
      ),
    );
  }
}

Only the Text widget rebuilds when the song changes — everything else stays put.

If you read this far, tweet to the author to show them you care. Tweet a Thanks
0 votes
0 votes

More Posts

Fixing a Tricky Playback Issue in My Flutter Music App

yogirahul - Jul 24

Why I Started Creating Models in My Flutter Project

yogirahul - Aug 20

Why does my Flutter music player jump from 0:00 to actual time? (One-liner fix inside!)

yogirahul - Aug 8

Flutter Event Management made easy

Somen Das - Apr 23

How I Paid Off a Little Technical Debt in My Flutter Music App

yogirahul - Aug 15
chevron_left