Home > Software design >  Using riverpod for simple state management is not reading state changes at UI
Using riverpod for simple state management is not reading state changes at UI

Time:02-22

I'm moving to Riverpod and starting with what should be a very simple boolean state management to switch the app theme. I've read many "how to" to do this exact thing, but none focus on the basics itself, are using outdated API, use different notifiers and providers, and add more features than needed. So now I'm lost.

versions:

  • shared_preferences: ^2.0.13
  • flutter_riverpod: ^1.0.3

Simple story:

  1. A model class that extends StateNotifier and has accessor and mutators in it for 1 single boolean value
  2. Very basic app stateful widget

Problem: I can see, via prints, the state is being altered, but the UI is not changing to when the state is changed. Am I not using StateNotifier and StateProvider properly for a simple boolean state?

Thank you for any insight and guidance! Code follows:

===

model class with accessors / mutators

class RiverThemeDarkModel  extends StateNotifier<bool> {

  RiverThemeDarkModel() : super(false) {}

  bool get isDark  {
    print("model asked for dark status: ${state}");
    return state;
  }

  toggleDark() {
    print("toggler has listeners: ${hasListeners}");
    state = !state;
    print("toggled to: ${state}");
  }

  set isDark(bool value) {
    state = value;
  }

}

Very basic app stateful widget

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

void main() {

  runApp(ProviderScope(child: MyApp()));
}

final themeProvider = StateProvider((ref) =>  RiverThemeDarkModel());

class MyApp extends ConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {

    final darkValueModel = ref.watch(themeProvider);

     print("consumer widget: dark value is ${darkValueModel.state} and listeners: ${darkValueModel.hasListeners}");
          return MaterialApp(
            title: 'Flutter Demo',
            debugShowCheckedModeBanner: false,
            theme: darkValueModel.isDark? ThemeData.dark() : ThemeData.light(),
            home: const MyHomePage(title: 'Flutter Demo Home Page'),
          );
        }

}

class MyHomePage extends ConsumerStatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  ConsumerState<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends ConsumerState<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    final darkValueModel = ref.watch(themeProvider);
print("build of state");
      return Scaffold(
        appBar: AppBar(
          title: Text(darkValueModel.isDark ? "Dark Mode" : "Light Mode"),
          actions: [
            IconButton(
                icon: Icon(darkValueModel.isDark
                    ? Icons.nightlight_round
                    : Icons.wb_sunny),
                onPressed: () {
                  ref.read(themeProvider).toggleDark();
                  print("Got pressed button, after setting is ${ref.read(themeProvider).isDark} \n\n");
                })
          ],
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text(
                'Something',
              ),
            ],
          ),
        ),
      );
    }
}

CodePudding user response:

From your code snippet, what you did here

final themeProvider = StateProvider((ref) =>  RiverThemeDarkModel());

was to provide to your UI access to the default state of your class which is false. thus if you change it to true and rerun the app it changes the theme. now what you want to do is expose access to the stateNotifier of your class which listens to the state changes of the class and notifies its listeners. To achieve this you need to use a StateNotifierProvider this way...

final themeProvider = StateNotifierProvider<RiverThemeDarkModel, bool>(
        (ref) =>  RiverThemeDarkModel());

Now the rest of the code to help your implementation is as follows

For your Model.

class RiverThemeDarkModel  extends StateNotifier<bool> {

  RiverThemeDarkModel() : super(false) {}

//u don't need this 'getter' piece of code 
  bool get isDark  {
    print("model asked for dark status: ${state}");
    return state;
  }

  toggleDark() {
    print("toggler has listeners: ${hasListeners}");
    state = !state;
    print("toggled to: ${state}");
  }

//neither do you need this
  set isDark(bool value) {
    state = value;
  }

}

For your UI...

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

void main() {

  runApp(ProviderScope(child: MyApp()));
}

final themeProvider = StateNotifierProvider<RiverThemeDarkModel, bool>(
        (ref) =>  RiverThemeDarkModel());

class MyApp extends ConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {

    final darkValueModel = ref.watch(themeProvider);

     print("consumer widget: dark value is ${darkValueModel.state} and listeners: ${darkValueModel.hasListeners}");
          return MaterialApp(
            title: 'Flutter Demo',
            debugShowCheckedModeBanner: false,
            theme: darkValueModel? ThemeData.dark() : ThemeData.light(),
            home: const MyHomePage(title: 'Flutter Demo Home Page'),
          );
        }

}

class MyHomePage extends ConsumerStatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  ConsumerState<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends ConsumerState<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    final darkValueModel = ref.watch(themeProvider);
print("build of state");
      return Scaffold(
        appBar: AppBar(
          title: Text(darkValueModel ? "Dark Mode" : "Light Mode"),
          actions: [
            IconButton(
                icon: Icon(darkValueModel
                    ? Icons.nightlight_round
                    : Icons.wb_sunny),
                onPressed: () {
                  ref.read(themeProvider.notifier).toggleDark();
                  print("Got pressed button, after setting is ${ref.read(themeProvider)} \n\n");
                })
          ],
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text(
                'Something',
              ),
            ],
          ),
        ),
      );
    }
}

Check out the documentation too. It clarifies some things you won't get from most tutorials. enter image description here

use this

final themeProvider = StateNotifierProvider<RiverThemeDarkModel, bool>(
    (ref) => RiverThemeDarkModel());

Instead of this

final themeProvider = StateProvider( (ref) => RiverThemeDarkModel());

Small Change on your code

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(ProviderScope(child: MyApp()));
}

final themeProvider = StateNotifierProvider<RiverThemeDarkModel, bool>(
    (ref) => RiverThemeDarkModel());

class MyApp extends ConsumerWidget {
   MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final darkValueModel = ref.watch(themeProvider);

    print(
        "consumer widget: dark value is ${ref.read(themeProvider.notifier).state} and listeners: ${ref.read(themeProvider.notifier).hasListeners}");
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme:darkValueModel
          ? ThemeData.dark()
          : ThemeData.light(),
      home:  MyHomePage(title: 'Flutter Demo Home Page${ref.read(themeProvider.notifier).isDark}'),
    );
  }
}

class MyHomePage extends ConsumerStatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  ConsumerState<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends ConsumerState<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    final darkValueModel = ref.watch(themeProvider);
    print("build of state");
    return Scaffold(

      appBar: AppBar(
        title: Text(ref.read(themeProvider.notifier).isDark
            ? "Dark Mode${widget.title}"
            : "Light Mode"),
        actions: [
          IconButton(
              icon: Icon(ref.read(themeProvider.notifier).isDark
                  ? Icons.nightlight_round
                  : Icons.wb_sunny),
              onPressed: () {
                ref.read(themeProvider.notifier).toggleDark();
                print(
                    "Got pressed button, after setting is ${ref.read(themeProvider.notifier).isDark} \n\n");
              })
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              "Something ${ref.read(themeProvider.notifier).isDark} ",
            ),
          ],
        ),
      ),
    );
  }
}

class RiverThemeDarkModel extends StateNotifier<bool> {
  RiverThemeDarkModel() : super(false) {}

  bool get isDark {
    print("model asked for dark status: ${state}");
    return state;
  }

  toggleDark() {
    print("toggler has listeners: ${hasListeners}");
    state = !state;
    print("toggled to: ${state}");
  }

  set isDark(bool value) {
    state = value;
  }
}
  • Related