Why theming in Flutter is confusing (to me) Part 2

Photo by Jess Bailey on Unsplash

Why theming in Flutter is confusing (to me) Part 2

In my previous article, I tried to explain why theming in Flutter confuses me.
I ended the article by saying the way ThemeData is implemented doesn't make sense to me.

A few weeks have passed and I've been playing with this in my head, and within my pet project. I had some new insights that I would like to share with you in this article.

But first, we need to make things clear

During the discussion, people were saying to me that Material Design is a good design system and it's not confusing to them.

And I agree that Material Design is a good system. It's well-documented and covers all varieties of devices and screen sizes.

So just to be clear, this article is not about criticizing Material Design. It's about how theming is implemented in Flutter.

The other thing that has been mentioned is that ThemeData is capable of implementing any design. This is also not something that I'm claiming Flutter is not capable of. But what we have to do to achieve it, is confusing to me.

New insights

In the last couple of weeks I've played around with theming in my pet project and I also started a new project at work.

Thinking about theming in both of these projects led me to new thoughts that might explain why it is confusing me.

Property overload (part 2)

In the previous article, I also talked about property overload. I talked about all these properties you have available to you in the ThemeData object, even though you might only need 1% of them.

People commented that why this is even a problem since I could "just not use them".

So I started thinking about this, and why it bothers me as much as it does. And I found the reason, which I will explain to you here.

Abstractions

First, we will have to talk about abstractions. Using abstractions is a common practice for us software developers.

We often use abstractions to make other people's code more our own and more specific to our needs.

Let's say we're building an app for our local swimming pool and they wish to show the weather forecast for the upcoming week.

We might want to use some 3rd party service like https://openweathermap.org/.

OpenWeather gives us a wide variety of endpoints and options to get weather data. But, for our use case, we only need the forecast for the upcoming week. So therefore we can make an abstraction of this service.

We could end up with a WeatherRepository. This repository only needs a single function, getWeekForecast(). It will call the OpenWeather endpoint for a forecast. The location can be hardcoded since it's always for the same location, which is the one of the swimming pool.

We have now made an abstraction of the OpenWeather API which is easy for us to use in our application.

Always ThemeData

In the example above I've described a common practice in software development about using abstractions to make other people's code our own.

So if we do it for other things, why not for theming?

Well, because you can't. Theming in Flutter is implemented in such a way that when you grab the Theme from context, it will always return a ThemeData object. And as we know, ThemeData contains all these fields, even though we don't need and use them.

In the previous article, I used the counter app as an example. Wouldn't it make the theme more clear if we could abstract away all the properties we don't use, and maybe give our texts a more describing name than bodyMedium and titleMedium in the process?

Why ThemeData

I've also thought about the existence of ThemeData. Why would they use it like this, and what is its purpose?

Global Theming

I realized that this is the only way to globally style the Material Widgets without wrapping them in a custom widget.

For example, it prevents us from having to put the same InputDecoration on every TextField.

Limitations

ThemeData is designed against the Material Design specifications.

And even though this can come across as flexible, it also is limited by it. You will especially experience this when you have to implement UI that is not (fully) based on Material Design.

An example. Material Design defines two types of text fields:

Side by side view two types of text fields, filled and outlined

The first is called Filled, and the second is called Outlined.

So what if we have the following design:

With ThemeData you can go a long way, but to get that label above the TextField, you will have to create a custom widget to make it easily reusable since we don't want to place the Text widget above every TextField manually, right?

Earlier, I mentioned that ThemeData was necessary to globally style Material Widgets without having to wrap them. But since we just created a custom component, let's call it MyTextField, we've just created a wrapper.

So now we could also define the styling directly in the component. And because we reuse the component, we are globally theming our app at the same time. There is no need for an external way of defining a style for this component anymore.

Dark and light mode

Okay, the above statement was not completely true. Most modern applications support both dark and light modes. So MyTextField should change according to the active mode.

To support dark and light modes in Flutter you define two ThemeData objects. So technically, you would be able to create two completely different Themes that don't even look like the same app because it gives you access to every material property.

In practice, this is hardly the case. More often it's just a difference in colors. A white background becomes dark grey and the black text becomes white.

So what we need is a theme that just holds the values that change between themes, nothing more.

Theme vs component properties

So what if we would style our MyTextField directly inside the class? This makes it easier to maintain because you don't have to deal with two different ThemeData classes. When the design changes, it most likely should be changed in both light and dark themes anyway.

Now we only have to find a way to allow MyTextField to adapt to light and dark mode changes.

After taking another quick look at the design, the only properties that need to change are:

  1. The color of the label

  2. The color of the Input Text

  3. The background color of the text field

So what if we could have a theme that just contains these kinds of properties?

NOTE: We could also check for the mode in the component but this is a bit ... mehh, IMO.

It would also limit to only dark and light modes while you might also want to support a special theme for people suffering from colorblindness later on.

Explicit theming

In the conclusion of the previous article, I dropped the term 'explicit theming'.

This is something I'm currently experimenting with in my pet project. It applies the things I mentioned in this article.

What I'm currently doing is breaking my app up into custom reusable components. These will ensure consistency across the app because they will contain most of the styling.

For the dark/light mode configurations, I'm creating a custom Theme class that only holds the properties that change when switching modes. In a sense, it is an abstraction of ThemeData that only gives me access to properties I use.

I'm still playing around with this but so far I like where it is going. If I'm a bit further with the development of my pet project and I'm still happy with it, I will dedicate an article to it.

Conclusion

I have a bit more understanding of why Theming in Flutter is confusing to me. I also have a better understanding of why ThemeData is 'needed'.

I'm still looking for a way of theming that fits me better and therefore I'm experimenting with something I'd like to call: 'explicit theming'.