Dave
Dave

Reputation: 5416

Flutter StatelessWidget and Provider not updating or calling build unnecessarily

I'm trying to use Provider with a Stateless widget page to update a countdown UI.

When I run all the below, I get both print messages in the console on each 1 second tick of the stopwatch:

e.g.

flutter: build()
flutter: _buildCountDown()

Also, the remaining value doesn't actually update in the UI.

But if I:

return Consumer<CountdownProvider>(
  builder: (_, countdownProvider, __) {
    return Text(countdownProvider.remaining.toString()),
});

...everything works as expected: the UI updates properly and I don't get the build() printout, just the _buildCountDown(), which is what I wanted.

Q) What am I doing wrong that means I can't make this a Stateless widget that updates as expected?


  1. main.dart I have the Provider like so:
return MultiProvider(
  providers: [        
    ChangeNotifierProvider(
      create: (_) => CountdownProvider(),
    ),
  ],
)
  1. I have written a simple countdown provider - CountdownProvider:
class CountdownProvider extends ChangeNotifier {

  int remaining = 0;
  final Stopwatch _stopwatch = Stopwatch();
  late Timer _timer;

  void start(int val) {
    remaining = val;
    _timer = Timer.periodic(const Duration(seconds: 1), _onTick);
    _stopwatch.start();
  }

  void _onTick(Timer timer) {
    remaining--;
    if (remaining < 0) {
      return;
    }
    notifyListeners();
  }

  void stop(bool skipNotify) {
    _timer.cancel();
    _stopwatch.stop();

    if (skipNotify) {
      _stopwatch.reset();
      remaining = 0;
      return;
    }
    notifyListeners();
  }
}
  1. Page to display it: PageCountdown:
class PageCountdown extends StatelessWidget {
  const PageCountdown({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 10 seconds default countdown
    Provider.of<CountdownProvider>(context, listen: false).start(10);

    print("build()");
    return Scaffold(
      body: _buildContent(context),
    );
  }

  Widget _buildContent(BuildContext context) {
    return SafeArea(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          _buildCountDown(context),
        ],
      ),
    );
  }

  Widget _buildCountDown(BuildContext context) {
    int remaining = Provider.of<CountdownProvider>(context).remaining;

    print("_buildCountDown()");

    return Text(
      remaining.toString(),
    );
  }
}

Upvotes: 2

Views: 779

Answers (1)

user18309290
user18309290

Reputation: 8300

PageCountdown is the single widget, which is rebuilt when the counter ticks. start(10) in the beginning of build will reset remaining to 10 every rebuild. Something like this will work:

class PageCountdown extends StatelessWidget {
  const PageCountdown({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 10 seconds default countdown
    Provider.of<CountdownProvider>(context, listen: false).start(10);

    print("build()");
    return const Scaffold(
      body: Countdown(),
    );
  }
}

class Countdown extends StatelessWidget {
  const Countdown({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          _buildCountDown(context),
        ],
      ),
    );
  }

  Widget _buildCountDown(BuildContext context) {
    int remaining = Provider.of<CountdownProvider>(context).remaining;

    print("_buildCountDown()");

    return Text(
      remaining.toString(),
    );
  }
}

Upvotes: 1

Related Questions