paint-brush
Flutter Riverpod: Use StateNotifier for Configuration and Update UI by@jimmysun
4,509 reads
4,509 reads

Flutter Riverpod: Use StateNotifier for Configuration and Update UI

by Jimmy SunMay 4th, 2021
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Flutter Riverpod: Use StateNotifier for Configuration and Update UI. The code is easy and convenient to use with Riverpod. Updateing a setting value not only updates the UI that listens to that value, but also updates other parts of the UI which don't listen to it, but listen to other setting values in the same state. Let's make a few changes to the code to update the UI and build Box A and Build Box B. Add 2 Providers to update a single state with a single value in the state.

Company Mentioned

Mention Thumbnail
featured image - Flutter Riverpod: Use StateNotifier for Configuration and Update UI
Jimmy Sun HackerNoon profile picture

I've been using Riverpod with StateNotifier to store my app's configurations / settings. It's easy and convenient as I can use methods to update the state's values. I can always use StateProvider but then I have updated the state directly, which is not convenient (for me) especially when I have a lot of setting parameters.

Using StateNotifier also works perfectly. Well, until recently.. then I found out that updating a setting value not only updates the UI that listens to that value, but also updates other parts of the UI that don't listen to it, but listen to other setting values in the same state. I hope you get what I mean from the last sentence.

To make it clear, let me show you a very simple demonstration.

  • We'll make 2 switches: Switch A and Switch B.
  • Switch A will build Box A, and should not build Box B.
  • Switch B will build Box B, and should not build Box A.

Let's code! You can put all the code below into 1 single dart file.

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

class Settings {
  final bool settingA;
  final bool settingB;

  Settings({
    this.settingA = false,
    this.settingB = false,
  });

  Settings copy({
    bool settingA,
    bool settingB,
  }) =>
      Settings(
        settingA: settingA ?? this.settingA,
        settingB: settingB ?? this.settingB,
      );
}

First, create the Settings class. The copy method will be used to update the State later.

final allSettingsProvider =
    StateNotifierProvider<SettingsNotifier>((ref) => SettingsNotifier());

class SettingsNotifier extends StateNotifier<Settings> {
  SettingsNotifier() : super(Settings());

  void updateSettingA(bool settingA) {
    final newState = state.copy(settingA: settingA);
    state = newState;
  }

  void updateSettingB(bool settingB) {
    final newState = state.copy(settingB: settingB);
    state = newState;
  }
}

Next, create the StateNotifier with 2 methods to update each Settings' values. You see here we use the copy method we created in the Settings class.

class TestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Center(child: BuildBoxA()),
            Center(child: BuildBoxB()),
          ],
        ),
      ),
    );
  }
}

class BuildBoxA extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    print('Building Box A');
    final bool settingA = watch(allSettingsProvider.state).settingA;
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Switch.adaptive(
          value: settingA,
          onChanged: (value) {
            context.read(allSettingsProvider).updateSettingA(value);
          },
        ),
      ],
    );
  }
}

class BuildBoxB extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    print('Building Box B');
    final bool settingB = watch(allSettingsProvider.state).settingB;
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Switch.adaptive(
          value: settingB,
          onChanged: (value) {
            context.read(allSettingsProvider).updateSettingB(value);
          },
        ),
      ],
    );
  }
}

Next is the UI. Like I said, it's a very simple demo. No need to think about refactoring here.

The result:

Not good, right? Turning on and off Switch A should only build Box A, but it also builds Box B. Why? It's because we replace the whole state with a new one. Every value in Settings gets updated.

Is there any way to update just a single value in the state? I couldn't find one. So I headed to Github and the nice people there pointed out that I don't need to worry about how I write to the state, but rather how I read it. There's an example in the official documentation here.

So, let's make a few changes to the code. Add 2 Providers and change BuildBoxA and BuildBoxB like this:

final settingAProvider =
    Provider<bool>((ref) => ref.watch(allSettingsProvider.state).settingA);
final settingBProvider =
    Provider<bool>((ref) => ref.watch(allSettingsProvider.state).settingB);

class BuildBoxA extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    print('Building Box A');
    final bool settingA = watch(settingAProvider);
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Switch.adaptive(
          value: settingA,
          onChanged: (value) {
            context.read(allSettingsProvider).updateSettingA(value);
          },
        ),
      ],
    );
  }
}

class BuildBoxB extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    print('Building Box B');
    final bool settingB = watch(settingBProvider);
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Switch.adaptive(
          value: settingB,
          onChanged: (value) {
            context.read(allSettingsProvider).updateSettingB(value);
          },
        ),
      ],
    );
  }
}

The result:

Exactly what I (we) wanted!