Learning
How to change the color of a Flutter SpinBox

How to change the color of a Flutter SpinBox

In a previous post I discussed about my first contribution to open source, and even if it didn’t turn out precisely as I wanted, I learnt a lot. And now I’m going to explore the solution to the issue I tried to resolve.

If you didn’t see my previous post, the issue was basically adding the option to apply the theme to the buttons of a spinbox in Flutter, because the theme was only applied when the widget was focused. This issue was valid to the material widget only, not the cupertino one.

The solution that was applied not only solves this particular problem, but also covers other scenarios. First using Material State Property, and then using an Inherited Theme to complement this functionality, but I think I’m moving too fast. Let’s start preparing a simple app to test this.

Preparing the app

Let’s execute flutter create in a CLI to generate our new Flutter app.

flutter create flutter_spinbox_examplesCode language: Bash (bash)

Now let’s get rid of all the unnecessary stuff and create 3 spinbox widget that will be our test subjects.

void main() {
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Spinbox Example'),
      ),
      body: ListView(
        children: [
          SpinBox(
            value: 10,
            decoration: const InputDecoration(labelText: 'Control'),
          ),
          SpinBox(
            value: 10,
            decoration: const InputDecoration(labelText: 'Material State Property'),
          ),
          SpinBox(
            value: 10,
            decoration: const InputDecoration(labelText: 'Inherited Theme'),
          ),
        ],
      ),
    );
  }

}
Code language: Dart (dart)

Three spinbox widgets

I think we can do better than that, let’s add some padding to the spinbox widgets.

      body: ListView(
        children: [
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: SpinBox(
              value: 10,
              decoration: const InputDecoration(labelText: 'Control'),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: SpinBox(
              value: 10,
              decoration: const InputDecoration(labelText: 'Material State Property'),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: SpinBox(
              value: 10,
              decoration: const InputDecoration(labelText: 'Inherited Theme'),
            ),
          ),
        ],
      ),
Code language: Dart (dart)
Three spinbox widgets with padding

That’s much better.

We can see that while the spinbox is unfocused it is gray, as if it is disabled. In some cases this behavior is not a problem, but when we have several spinboxes we can have a better appearance if we match them with the theme, no matter the state of the widgets.

Now let’s start with the tests.

Material State Property

With Material State Property you can resolve attributes like the color of the widgets, using its method resolveWith.

In MaterialState you have the following states: disabled, dragged, error, focused, hovered, pressed, scrolledUnder and selected. In the cas of the Spinbox the state that it exposes are disabled, focused and error.

We are going to resolve this 3 states in our second spinbox for the iconColor property.

          Padding(
            padding: const EdgeInsets.all(16.0),
            child: SpinBox(
              value: 10,
              decoration: const InputDecoration(labelText: 'Material State Property'),
              iconColor: MaterialStateProperty.resolveWith((states) {
                  if (states.contains(MaterialState.disabled)) {
                    return Colors.grey;
                  }
                  if (states.contains(MaterialState.error)) {
                    return Colors.red;
                  }
                  if (states.contains(MaterialState.focused)) {
                    return Colors.green;
                  }
                }
              ),
            ),
          ),
Code language: Dart (dart)

But now the only thing that changed is that the button icons are now black. This is because in this moment the spinbox doesn’t have any state. If you tap on the spinbox the button icons are going to be green, but as soon as you change the focus the color goes back to black.

What we need, is to resolve the color when the state is not between those 3 states that are exposed. So we are going to add a default color, returning Colors.blue after the if statements.

return Colors.blue;
Code language: Dart (dart)

Second spinbox with MaterialStateProperty

Now the buttons icon color is going to be blue by default, until you tap on it, then it is going to be green.

For the next option we are going to explore how to achieve the same thing, but for several spinbox at a time.

Inherited Theme

Now that we know how to use MaterialStateProperty to change the color of our spinbox, if we have several spinboxes across our application we probably don’t want to repeat this code in every spinbox, so for that we have the SpinBoxTheme widget.

SpinBoxTheme is an InheritedWidget, which means any data we define in this widget is going to be spread down the widget tree.

To see SpinBoxTheme in action we need to add one more spinbox, so lets add it and wrap both with a Column widget.

            Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: SpinBox(
                    value: 10,
                    decoration: const InputDecoration(labelText: 'Inherited Theme 1'),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: SpinBox(
                    value: 10,
                    decoration: const InputDecoration(labelText: 'Inherited Theme 2'),
                  ),
                ),
              ]
            )
Code language: Dart (dart)

And now wrap the Column with a SpinBoxTheme, you will notice the data property is mandatory. This property is of the class SpinBoxThemeDate and the attribute we are going to define is iconColor. The same property we define in our previous example. Here we are going to define our MaterialStateProperty.

            SpinBoxThemeData(
              iconColor: MaterialStateProperty.resolveWith((states) {
                return Colors.red;
              })
            )Code language: Dart (dart)

Since we already covered the state in the previous section, we are going to keep it simple and return just the red color regardless of the state, now this is the final code for the SpinBoxTheme with both spinboxes within:

          SpinBoxTheme(
            data: SpinBoxThemeData(
              iconColor: MaterialStateProperty.resolveWith((states) {
                return Colors.red;
              })
            ),
            child: Column(
              children: [
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: SpinBox(
                    value: 10,
                    decoration: const InputDecoration(labelText: 'Inherited Theme 1'),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: SpinBox(
                    value: 10,
                    decoration: const InputDecoration(labelText: 'Inherited Theme 2'),
                  ),
                ),
              ]
            )
          ),
Code language: Dart (dart)
Third and fouth spinbox with SpinBoxTheme

Precedence Order

As a final point, I wan to add that there is an precedence order in this methods to use them simultaneously in our app.

  1. MaterialStateProperty in the SpinBox iconColor property.
  2. MaterialStateProperty in the SpinBoxTheme, the closest one in the widget tree is taken.
  3. Theme colors: This are only applied when the widget is focused.

If you have several SpinBoxTheme widgets in the widget tree, the spinbox is going to resolve the closest one in the widget tree. For example you can have a SpinBoxTheme at the top of you entire application, and another one for only a group of spinboxes, the closest one is going to take precedence.

To test this you can wrap the MaterialApp widget with a SpinBoxTheme that returns the yellow color for all states and you will see that only the first spinbox is going to change to yellow.

And now if you remove the SpinBoxTheme that defines the red color, all spinboxes are going to be yellow except for the second one that has defined its iconColor property.

@override
  Widget build(BuildContext context) {
    return SpinBoxTheme(
      data: SpinBoxThemeData(
        iconColor: MaterialStateProperty.resolveWith((states) => Colors.yellow),
      ),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHomePage(),
      ),
    );
  }
Code language: Dart (dart)

Final words

I hope you found this post useful. As always here is the source code so you can explore it and test it yourself.

Tags :

Leave a Reply

Your email address will not be published. Required fields are marked *