A guide to theming your app in Flutter

Matt Angelosanto - Aug 19 '21 - - Dev Community

Written by Chinedu Imoh ✏️

What is theming?

Theming is the generic name for everything that styles an application to make it look appealing, attractive, and suit your tastes or needs. It’s what makes your app beautiful, and as personal preferences differ, the specifics behind this can vary across different people.

Factors like text styles, text colors, icon styles, and more all contribute to the theme of an application. Everything that makes an application appealing to users comes from theming, and honing your knowledge of Flutter themes will go a long way toward making you a more proficient Flutter developer.

What can you use to theme an app in Flutter?

Flutter’s MaterialApp comes with a default light blue theme. You see it when you launch the default counter app: it’s plain blue and white.

However, as we highlighted earlier, there are many ways to theme an application that range from altering the look of simple text and icon style, to using styling widgets like the Container widget, TextButton, or RawMaterialButton. There is also complex switching between light and dark theme modes in an application.

All of these contribute to the overall theme of an application. This article will touch on each of these methods for theming a Flutter application.

Theming widgets and containers

In this section, we’ll discuss how to style widgets and containers in Flutter by styling a text widget and the Container widget. Flutter provides a TextStyle class that contains several properties that can modify the look and feel of the text widget.

Text("I went for a walk")
Enter fullscreen mode Exit fullscreen mode

Text widget demo

Flutter renders this text on the screen with the default color, size, structure, and weight. We can now add some styling to enhance the look and feel of the app. We’ll do that by adjusting the properties that the TextStyle class offers.

 Text("I went for a walk", 
 style: TextStyle(
   color: Colors.blue, 
   weight: FontWeight.bold, 
   fontSize: 18,
   ),
  ),
Enter fullscreen mode Exit fullscreen mode

The same text widget demo, with custom font color

By adjusting the properties of the TextStyle class, we have changed the look and feel of the text, and added interest to the overall theme of the application.

The process for styling the Container widget is similar to what we did for TextStyle; however, we achieve styling here by adjusting the properties of the BoxDecoration class, which are then passed to the decoration parameter of the Container widget.

Container(
  height: 48,
  margin: const EdgeInsets.all(16),
  decoration: BoxDecoration(
  color: Colors.grey[300],
  borderRadius: const BorderRadius.all(
   Radius.circular(16),
  ),
  border: Border.all(
   color: Colors.blue,
   width: 2,
  ),
  ),
 );
Enter fullscreen mode Exit fullscreen mode

The container as customized in the code block

Above, we assigned the container a color (grey) and gave it a blue border by adjusting the borderColor property. That gives us a blue line that is 2px in width around the container itself.

Another styling choice we made in the code block above is altering the border radius; we gave it a circular border radius of 16px. That provides the container with an excellent rounded shape at the edges instead of the flat edges that comes with the default container.

Lastly, we gave the entire container a margin of 16px, which leaves a space of 16px on each side of the container.

Using the Material ThemeData class

Both of the previous scenarios we've discussed — the TextStyle class and the Container widget — deal with the styling of a single component for that particular screen, and do not affect the styling of other TextStyle classes or Container widgets.

Now, suppose we desire to share a specific theme — including the color, brightness, weight, icon styles, and text styles — across our entire app. We can achieve that using the ThemeData class, which provides us with many features to meet our tastes.

Consider the code block below.

MaterialApp(
 title: 'Guide to Theming in Flutter',
 theme: ThemeData(
 brightness: Brightness.dark,
 primaryColor: Colors.lightBlue[800],
 accentColor: Colors.cyan[600],
 fontFamily: 'Georgia',
 textTheme: TextTheme(
  headline1: TextStyle(fontSize: 50, fontWeight: FontWeight.bold),
  headline6: TextStyle(fontSize: 30.0, fontStyle: FontStyle.italic),
  bodyText2: TextStyle(fontSize: 18.0, fontFamily: 'Hind'),
 ),
 ),
 home: MyHomePage(),
);
Enter fullscreen mode Exit fullscreen mode

The ThemeData class provides properties we can modify to adjust the theme of our application. The brightness property deals with the theme’s overall brightness and affects the text colors in the app, as text color becomes black with Brightness.light and white with Brightness.dark.

The primaryColor property affects the background color for significant parts of the app, including toolbars, tab bars, and app bars.

The accentColor property affects the foreground color for widgets, and also refers to the secondary color. The font family, as well as the different categories for the textTheme, have all been modified, as seen in the code block above.

Flutter assigns values to each of the headline and bodyText categories by default, but each of them can be adjusted to fit our taste in design.

To specifically use any of the declared themes in any part of the app, we simply have to call the one we want, as shown below.

Container(
 color: Theme.of(context).accentColor,
 child: Text(
 'Theming in Flutter',
 style: Theme.of(context).textTheme.headline6,
 ),
),
Enter fullscreen mode Exit fullscreen mode

Material's ThemeData class in action

Above, we style the text with the declared headline6 values, meaning the font size will be 36 and the font style will be italics. This method for styling is pretty handy and reduces the number of lines of code that will repeat across the app if we were to style each of them individually.

Light and dark theme modes

The last type of theming we’ll discuss is the implementation of light and dark theme modes for Flutter apps. Dark theme modes are commonly used in applications to save energy for battery life and reduce stress to the eyes in low-light physical settings.

In this tutorial, we’ll make use of the adaptive_theme package to facilitate this. This package provides many excellent out-of-the-box functionalities to ensure the smooth setting and transitioning of different theme modes. It also gets the most current theme used every time the app restarts.

In addition to adaptive_theme, we’ll also make use of the Riverpod package.

Start by adding the code below under the dependencies block in the pubspec.yaml file.

dependencies:
 adaptive_theme: ^2.2.0
 flutter_riverpod: ^0.14.0+3
Enter fullscreen mode Exit fullscreen mode

Run the flutter pub get command in the terminal. This command will get both packages downloaded and ready to use in your codebase.

flutter pub get
Enter fullscreen mode Exit fullscreen mode

Head over to the main.dart file and import the adaptive_theme and Riverpod packages.

import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
Enter fullscreen mode Exit fullscreen mode

Create a variable named savedThemeMode to access the user’s most recently used mode at the restart of the application. Make the main block asynchronous and await the getThemeMode function that the adaptive_theme package provides.

The next thing to do is wrap the entire app in a ProviderScope class to access the providers used in the app.

Future<void> main() async {
 WidgetsFlutterBinding.ensureInitialized();
 final savedThemeMode = await AdaptiveTheme.getThemeMode();
 runApp(ProviderScope(child: MyApp(savedThemeMode: savedThemeMode)));
}
Enter fullscreen mode Exit fullscreen mode

Create a variable to receive the savedThemeMode from the main block above. Wrap the entire MaterialApp with an adaptive_theme class, which gives us access to its properties so that we can customize the light and dark theme modes for the application.

class MyApp extends StatelessWidget {
 final AdaptiveThemeMode? savedThemeMode;
 // ignore: use_key_in_widget_constructors
 const MyApp({this.savedThemeMode});
 @override
 Widget build(BuildContext context) {
 return AdaptiveTheme(
  light: ThemeData(
  brightness: Brightness.light,
  primaryColor: Colors.green,
  scaffoldBackgroundColor: Colors.white,
  ),
  dark: ThemeData(
  brightness: Brightness.dark,
  primaryColor: Colors.red,
  scaffoldBackgroundColor: Colors.black,
  iconTheme: const IconThemeData(
   color: Colors.black,
  ),
  ),
Enter fullscreen mode Exit fullscreen mode

We pass ThemeData to the light property for the adaptive_theme class, into which we put all the variables we wish to customize. We then customize the brightness, primaryColor, and scaffoldBackgroundColor. The same thing gets done to the dark property. We modify the properties inside the ThemeData class and pass them to the property provided by adaptive_theme.

Next is the initial property we pass in the savedThemeMode, and if peradventure is null (e.g., if the app is newly installed), it defaults to the light theme mode. Then, lastly, we pass the MaterialApp class to the builder function and pass the declared themes to its properties.

 initial: savedThemeMode ?? AdaptiveThemeMode.light,
  builder: (theme, dark) {
  return MaterialApp(
  title: 'Article Light/Dark Theme',
  theme: theme,
  darkTheme: dark,
  home: const HomePage(),
  );
  },
 );
 }
}
Enter fullscreen mode Exit fullscreen mode

To switch between the light and dark theme modes, we will use a RawMaterialButton and pass in the toggleMode function of the adaptive_theme to its onPressed property. This button allows the user to switches between the two modes by clicking or tapping the button.

class HomePage extends StatelessWidget {
 const HomePage({Key? key}) : super(key: key);
 @override
 Widget build(BuildContext context) {
 return Scaffold(
  appBar: AppBar(
  title: const Text('Light/Dark theme in Flutter'),
  ),
  body: Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
   Center(
   child: RawMaterialButton(
    child: const Text(
    'Switch Modes',
    style: TextStyle(
     fontSize: 20,
     fontWeight: FontWeight.bold,
    ),
    ),
    onPressed: () {
    AdaptiveTheme.of(context).toggleThemeMode();
    },
    fillColor: Colors.green,
    padding: const EdgeInsets.all(16),
    shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(16),
    ),
   ),
   ),
  ],
  ),
 );
 }
}
Enter fullscreen mode Exit fullscreen mode

Finally, the images below show the light and dark theme modes as they’ll appear when toggled.

The app with the dark theme mode enabledThe app with the light theme mode enabled

Conclusion

Customizing various properties to create your desired theme can be pretty exciting, especially as it makes the app relate to your taste in design. With what you've learned so far, you can now style individual classes, the Container widget, and even give your app custom light and dark theme modes.


LogRocket: Full visibility into your web apps

LogRocket Dashboard Free Trial Banner

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Try it for free.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player