Flutter - How to Use MaterialStateProperty

This tutorial shows you how to use MaterialStateProperty in Flutter.

When using a Flutter widget, you may find an argument whose type is MaterialStateProperty. For example, the backgroundColor argument of SearchBar widget uses MaterialStateProperty<Color?> as the type. Meanwhile, the type of SearchBar's elevation is MaterialStateProperty<double?>. In this tutorial, I am going to explain how to create a value with that type and how it can be resolved to a value.

What's MaterialStateProperty

Flutter has an abstract class MaterialStateProperty which is an interface for classes that resolve to a value based on the value of MaterialState. Therefore, it can be useful for cases where the value might depend on the states of the widget. With this way, it becomes possible to define different values for each MaterialState. The MaterialState itself is an enum whose values are:

  • hovered: when the user drags the cursor over the widget.
  • focused: when the widget is in focus (e.g. when a TextField is tapped).
  • pressed: when the widget is being pressed down.
  • dragged: when the widget is being dragged.
  • selected: when the widget has been selected.
  • scrolledUnder: when the widget overlaps the content of a scrollable below.
  • disabled: when the widget is disabled.
  • error: when the widget is in an invalid state.

It's possible that a widget has more than one state at the same time.

MaterialStateProperty has a parameterized type. The purpose is to make sure that it can only resolve to values with a particular type. The code won't be able to compile if the return type is different from the parameterized type.

By using a MaterialStateProperty as the type, one argument can be used to return different values based on the states. It also provides a clear way to define the value to be used in each state.

Creating a MaterialStateProperty

If you have to create a value for an argument whose type is a MaterialStateProperty, below are several ways to create it.

Using MaterialStateProperty.resolveWith

This static method requires you to pass a function that accepts one parameter and return a value. The function's parameter type is Set<MaterialState> which represents the current states of the widget. Therefore, you can use the states to determine what value should be returned. The type of the returned value must comply with the parameterized type.

  static MaterialStateProperty<T> resolveWith<T>(T function Set<MaterialState> callback)

In the example below, we create a FilledButton which has a different color when the button is being pressed.

  ButtonStyle(
    backgroundColor: MaterialStateProperty.resolveWith((states) {
      if (states.contains(MaterialState.pressed)) {
        return Colors.pinkAccent;
      } else {
        return Colors.teal;
      }
    }),
  )

Output:

Flutter - MaterialStateProperty.resolveWith

Using MaterialStateProperty.all or MaterialStatePropertyAll

MaterialStateProperty.all is suitable if the returned value is always the same for all states.

  static MaterialStateProperty<T> all<T>(T value)

Example:

  ButtonStyle(
    backgroundColor: const MaterialStateProperty.all(Colors.teal),
  )

Output:

Flutter - MaterialStateProperty.all

If you need a const value, the alternative is to use MaterialStatePropertyAll.

  const MaterialStatePropertyAll(T value)

Example:

  ButtonStyle(
    backgroundColor: const MaterialStatePropertyAll(Colors.teal),
  )

Using MaterialStateProperty.lerp

There is another static constructor namedlerp that can be used to linearly interpolate between two MaterialStatePropertys.

  static MaterialStateProperty<T?>? lerp<T>(
    MaterialStateProperty<T>? a,
    MaterialStateProperty<T>? b,
    double t,
    T? Function(T?, T?, double) lerpFunction,
  )

There are four positional arguments. The first two arguments a and b are the MaterialStatePropertys to be lerped.

The third argument t represents position on the timeline. If the value is 0.0, the interpolation has not started and the returned value is equivalent to the value from a. If the value is 1.0, the interpolation has ended and the returned value is equivalent to the value from b. If the value is between 0.0. and 1.0, the interpolation is somewhere between a and b. It also supports values below 0.0. and greater than 1.0 which means it's being extrapolated.

The last argument is a function that's responsible to return a value based on the other three arguments. For certain types, Flutter provides a static function that can be passed as the argument. For example, you can use Color.lerp if the type is Color unless you want to create a custom logic.

If you use lerp, Flutter will try to resolve values from both a and b based on the current states. Then, the value will be lerped using the lerpFunction which uses the t value to compute the return value.

  ButtonStyle(
    backgroundColor: MaterialStateProperty.lerp<Color?>(
      const MaterialStatePropertyAll(Colors.red),
      const MaterialStatePropertyAll(Colors.yellow),
      0,
      Color.lerp,
    ),
  )

Flutter - MaterialStateProperty.lerp

Resolving a Value

If you have a widget whose parameter is a MaterialStateProperty, you may need to resolve it to a value based on the current states. Below are the examples.

Using MaterialStateProperty.resolve

The resolve method can be used to resolve the value of a MaterialStateProperty instance based on a set of states.

  T resolve(Set<MaterialState> states);

Example:

  MaterialStateProperty<Color> msp = MaterialStateProperty.resolveWith((states) {
    if (states.contains(MaterialState.pressed)) {
      return Colors.pinkAccent;
    } else {
      return Colors.teal;
    }
  });
  Color color = msp.resolve({ MaterialState.pressed, MaterialState.error });

Using MaterialStateProperty.resolveAs

Another way is to use the resolveAs static method.

  static T resolveAs<T>(T value, Set<MaterialState> states)

If the passed value is a MaterialStateProperty, it will resolve the value based on the given states. Otherwise, it will return the value itself. This method can be useful inside a widget whose parameter can be a MaterialStateProperty or another type of value.

For example, the mouseCursor of InkWell can be a MouseCursor or a MaterialStateMouseCursor, which implements MaterialStateProperty<MouseCursor>. If you look at the source code of the InkWell, it has the code below to resolve the MouseCursor value.

  final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
    widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
    statesController.value,
  );

Summary

In this tutorial, we have learned how to create a MaterialStateProperty value. You can use the resolveWith method if you need to return different values based on the current states. If the returned value is always the same, use the all method or MaterialStatePropertyAll constructor. For resolving a MaterialStateProperty to a value, call the resolve method of the instance or the resolveAs static method.