Theming a Flutter App: Getting Started
Learn how to make your app stand out by styling widgets, creating a dynamic theme, and toggling between available themes. By Andres Torres.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Theming a Flutter App: Getting Started
15 mins
One of the great features of Flutter is that it gives you absolute control of every pixel on the screen. This allows developers to implement the designer’s vision without compromise.
Nowadays, vanilla widgets look a bit dull. Customizing your widgets provides hierarchy, directionality and structure to the whole UI — helping with user acquisition and engagement.
In this tutorial, you’ll apply styles to widgets, centralize your app’s theme and dynamically toggle between different themes.
In the process, you’ll learn how to:
- Style specific widgets.
- Use an overall theme for your app.
- Toggle between light and dark themes.
Now, it’s time to embellish your project with beautiful themes!
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial. Then, open the starter project in Android Studio, where you’ll find Knight and Day, an activity-logging app for knights on duty.
Build and run. You’ll see the following screen:
On the Home screen, you’ll see a count of different activities you, as a knight, have logged. Pressing a button corresponding to one of the activities increments the activity count.
Even though the app works, it looks bland. You’ll use different methods to give the widgets some personality, making your app stand out from all the others in the ecosystem. :]
Before you start coding, take a moment to review some background about styling and theming and why they’re important.
What Is Theming?
Theming is the process of using a set of colors, fonts, shapes and design styles throughout your app. It’s a way to centralize all your stylistic decisions in one place.
Since most of your widgets share similar styles, you don’t want to style widgets individually. Instead, you should define the styles in one place and then reuse them. This will keep your codebase DRY (Don’t Repeat Yourself), readable and easy to maintain.
These are best practices to keep in mind when developing your app. If this doesn’t make sense right now, don’t worry! You’ll theme the app from the ground up and quickly realize the benefits of organizing your styles this way.
Theming in Flutter
Most visual widgets in Flutter have a style
property whose type varies depending on the widget. Examples include TextStyle
for the Text
widget or ButtonStyle
for the Button
widget. Specifying a style will only affect that specific widget.
The idiomatic approach to styling components in Flutter is to add Theme
widgets to the widget hierarchy. The higher level Material and Cupertino libraries even provide their own baked in themes to match the design languages they are implementing.
The Theme
widget automatically applies its style to all descendant widgets. It takes a ThemeData
argument, which holds the actual definition for the colors and font styles. If you look into the Flutter source code, you’ll even notice that a Theme
widget just uses an InheritedWidget
under the hood to distribute the ThemeData throughout the widget tree.
Applying a theme in Flutter is pretty straightforward. You’ll see how to do it next!
ThemeData
using Theme.of
. This is useful for making one-off variations based on the inherited style using copyWith
to overwrite attributes.Styling Widgets
The first thing you’ll learn is how to style specific widgets independently from one another.
Open lib/home/home_page.dart and go to the build
method. There are three plain RaisedButton
widgets. Your first task is to add shape
and color
attributes to style the buttons.
To start, replace the build
method with the following:
@override
Widget build(BuildContext context) {
final totalActivities = _joustCounter + _breakCounter + _patrolCounter;
return Scaffold(
appBar: CustomAppBar(
title: 'Knight and Day',
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BarChart(
joustCounter: _joustCounter,
breakCounter: _breakCounter,
patrolCounter: _patrolCounter,
),
const SizedBox(
height: 32.0,
),
Text('You\'ve done $totalActivities activities in total'),
const SizedBox(
height: 32.0,
),
RaisedButton(
child: const Text('Joust'),
onPressed: () => setState(() { _joustCounter++; }),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18.0)),
color: CustomColors.lightPurple,
),
RaisedButton(
child: const Text('Take break'),
onPressed: () => setState(() { _breakCounter++; }),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18.0)),
color: CustomColors.lightPurple,
),
RaisedButton(
child: const Text('Patrol'),
onPressed: () => setState(() { _patrolCounter++; }),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18.0)),
color: CustomColors.lightPurple,
),
],
),
);
}
Now all of the RaisedButton
widgets have a shape
attributes to make them into rounded rectangles and they’ve been given a light purple color
Build and run and you’ll see your new, fancy buttons.
CustomColors
from lib/theme/colors.dart. Following the DRY principle, this is a class that simply holds static values for the different colors you’ll use in this tutorial. If you need to change a color for any reason, instead of going through your entire codebase and changing each individual value, you can open CustomColors
and simply change it there.Providing an Overall Theme
You’ve seen how easy it’s to adjust the styling of a couple of widgets. When the number of widgets you want to style grows, however, it can become cumbersome to update all of those widgets when you want to change your apps style. Ideally, you want to have a single place that defines styles for your whole app. Luckily, you can achieve this by setting a Theme
for your MaterialApp
!.
Create a new file under lib/theme, name it custom_theme.dart and add the following code to the empty file:
import 'package:flutter/material.dart'; import 'colors.dart'; class CustomTheme { static ThemeData get lightTheme { //1 return ThemeData( //2 primaryColor: CustomColors.purple, scaffoldBackgroundColor: Colors.white, fontFamily: 'Montserrat', //3 buttonTheme: ButtonThemeData( // 4 shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18.0)), buttonColor: CustomColors.lightPurple, ) ); } }
You probably already have the hang of most of the code above. To break it down:
- You’re providing a static getter that’s globally accessible. You’ll use it later on.
- You’re building your actual custom
ThemeData
here. Notice the number of attributes you’ll override — this is just a handful of the possibilities. - You’re also defining the font family your text will take by default.
- Here, you define button styling, similar to what you did previously in lib/home/home_page.dart.
Now, the only thing left to do is apply the theme. Open the lib/main.dart file and replace the contents of the file with the following:
import 'package:flutter/material.dart';
import 'package:knight_and_day/home/home_page.dart';
import 'package:knight_and_day/theme/custom_theme.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Knight and Day',
home: HomePage(),
theme: CustomTheme.lightTheme,
);
}
}
The only thing that’s actually changed is you’re using the theme
attribute of MaterialApp
and supplying the custom theme you created in the previous step.
Build and run and you’ll see that the font family has changed globally.
Now that you’ve setup a global theme for your apps colors, text, and button styles you’ll want to remove the one off styles you added to the RaisedButton
s earlier. Revert the lib/home/home_page.dart build
method back to its original code:
@override
Widget build(BuildContext context) {
final totalActivities = _joustCounter + _breakCounter + _patrolCounter;
return Scaffold(
appBar: CustomAppBar(
title: 'Knight and Day',
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BarChart(
joustCounter: _joustCounter,
breakCounter: _breakCounter,
patrolCounter: _patrolCounter,
),
const SizedBox(
height: 32.0,
),
Text('You\'ve done $totalActivities activities in total'),
const SizedBox(
height: 32.0,
),
RaisedButton(
child: const Text('Joust'),
onPressed: () => setState(() { _joustCounter++; }),
),
RaisedButton(
child: const Text('Take break'),
onPressed: () => setState(() { _breakCounter++; }),
),
RaisedButton(
child: const Text('Patrol'),
onPressed: () => setState(() { _patrolCounter++; }),
),
],
),
);
}
Build and run again, and you’ll notice that the buttons are still styled even though you removed the styles from each RaisedButton
. They’re now inheriting the button theme from the theme you added to MaterialApp
earlier on.
Great job! You now have a solid foundation for theming your app.