How Flutter theme changes App design

We can use the Flutter theme property of the MaterialApp Widget, to control the design across the entire app. However, we can do that in various ways.

Previously, we have been building an interesting Quiz App. While building the app, we have learned a few important concepts on theme. We have used a custom theme class where we have declared many static constant Color properties.

And later, we have used those properties in our Flutter app.

Let us see how our previous Quiz App looks like.


Custom theme Flutter
Custom theme Flutter

As we see, the Flutter app uses a dark theme. And it was greenish.

Now, here is the most important question. Can we make this color scheme light replacing the green by pinkish color?

We want to do that centrally, from the same custom theme class.

The answer is, yes, we can.

Not only that, we can add many other features, which will make our Quiz App more interactive.

Let us see what we want to do first.

After that, we will learn how to do that.


Flutter theme changes entire app design
Flutter theme changes entire app design

Our “Play with Lexis Quiz App” is working the same way. We have not changed the business logic part. Therefore, we are not going to discuss that part.

If you want to know how we can map a list, please read the previous articles. We have discussed how we can use List data type in Flutter to make an interactive app.

In this section, we will concentrate on the Flutter theme part.

Firstly, we will build a custom ThemeData function that will return our custom theme to the MaterialApp theme property.

import 'package:flutter/material.dart';

import '../model/quiz_theme.dart';
import 'quiz_page.dart';

QuizTheme myTheme = QuizTheme();

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      home: const QuizPage(),

      /// we've started changing and building new theme
      /// from this point
      theme: myTheme.buildTheme(),
    );
  }
}

Secondly, our newly created “myTheme” object calls the custom ThemeData function “buildTheme()”. After that, it returns the value to the theme property of MaterialApp Widget.

Finally, we must take a look at the custom theme class. We have changed it a lot.

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

/// In a custom theme page we have described color and fonts
/// We may add more custom theme-features later
///

class QuizTheme {
  static const Color primaryColor = Color(0xFF409B25);
  static const Color scaffoldBackgroundColor = Color(0xFF2C6F2E);
  static const Color appBarBackgroundColor = Color(0xFF2C6F2E);
  static const Color boxDecorationColor = Color(0xFFC5DA28);
  static const Color elevatedButtonPrimaryColor = Color(0xFF3C9415);
  static const Color dividerColor = Color(0xFFD9DB26);
  static const correctAnswerColor = Color(0xFFFACAFA);
  static const questionTextColor = Color(0xFFF8E1F8);
  static const answerColor = Color(0xFFFFFFFF);

  static TextStyle answerStyle = GoogleFonts.langar(
    textStyle: const TextStyle(
      color: QuizTheme.answerColor,
      fontSize: 20.0,
      fontWeight: FontWeight.bold,
    ),
  );

  static TextStyle questionStyle = GoogleFonts.laila(
    textStyle: const TextStyle(
      color: QuizTheme.shrineBrown600,
      fontSize: 30.0,
      fontWeight: FontWeight.bold,
    ),
  );

  static TextStyle appbarStyle = GoogleFonts.salsa(
    textStyle: const TextStyle(
      color: QuizTheme.shrineBrown600,
      fontSize: 20.0,
      fontWeight: FontWeight.bold,
    ),
  );

  ThemeData _buildShrineTheme() {
    final ThemeData base = ThemeData.light();
    return base.copyWith(
      colorScheme: _shrineColorScheme,
      toggleableActiveColor: shrinePink400,
      primaryColor: shrinePink100,
      primaryColorLight: shrinePink100,
      scaffoldBackgroundColor: shrineBackgroundWhite,
      cardColor: shrineBackgroundWhite,
      textSelectionTheme:
          const TextSelectionThemeData(selectionColor: shrinePink100),
      errorColor: shrineErrorRed,
      buttonTheme: ButtonThemeData(
        colorScheme: _shrineColorScheme.copyWith(primary: shrinePink400),
        textTheme: ButtonTextTheme.normal,
      ),
      primaryIconTheme: _customIconTheme(base.iconTheme),
      textTheme: _buildShrineTextTheme(base.textTheme),
      primaryTextTheme: _buildShrineTextTheme(base.primaryTextTheme),
      iconTheme: _customIconTheme(base.iconTheme),
    );
  }

  ThemeData buildTheme() {
    return _buildShrineTheme();
  }

  IconThemeData _customIconTheme(IconThemeData original) {
    return original.copyWith(color: shrineBrown900);
  }

  TextTheme _buildShrineTextTheme(TextTheme base) {
    return base
        .copyWith(
          caption: base.caption!.copyWith(
            fontWeight: FontWeight.w400,
            fontSize: 14,
            letterSpacing: defaultLetterSpacing,
          ),
          button: base.button!.copyWith(
            fontWeight: FontWeight.w500,
            fontSize: 14,
            letterSpacing: defaultLetterSpacing,
          ),
        )
        .apply(
          fontFamily: 'Rubik',
          displayColor: shrineBrown900,
          bodyColor: shrineBrown900,
        );
  }

  static const ColorScheme _shrineColorScheme = ColorScheme(
    primary: shrinePink100,
    secondary: shrinePink50,
    surface: shrineSurfaceWhite,
    background: shrineBackgroundWhite,
    error: shrineErrorRed,
    onPrimary: shrineBrown900,
    onSecondary: shrineBrown900,
    onSurface: shrineBrown900,
    onBackground: shrineBrown900,
    onError: shrineSurfaceWhite,
    brightness: Brightness.light,
  );

  static const Color shrinePink50 = Color(0xFFFEEAE6);
  static const Color shrinePink100 = Color(0xFFFEDBD0);
  static const Color shrinePink300 = Color(0xFFFBB8AC);
  static const Color shrinePink400 = Color(0xFFEAA4A4);

  static const Color shrineBrown900 = Color(0xFF442B2D);
  static const Color shrineBrown600 = Color(0xFF7D4F52);

  static const Color shrineErrorRed = Color(0xFFC5032B);

  static const Color shrineSurfaceWhite = Color(0xFFFFFBFA);
  static const Color shrineBackgroundWhite = Colors.white;

  static const defaultLetterSpacing = 0.03;
}

As we see, there are lots of constant Color properties. Besides, we have built custom ThemeData, TextTheme, and IconThemeData instance methods.

As a result, later, we have used them as necessary to give our Quiz App a complete new look.

Quite naturally, we have modified the code of “quiz_page.dart”, as well. So that we can accommodate the bottom navigation bar.

import 'package:flutter/material.dart';
import 'package:rflutter_alert/rflutter_alert.dart';

import '../model/questions_list.dart';
import '../model/quiz_theme.dart';
import 'answer.dart';
import 'question.dart';

class QuizPage extends StatefulWidget {
  const QuizPage({Key? key}) : super(key: key);

  @override
  State<QuizPage> createState() => _QuizPageState();
}

class _QuizPageState extends State<QuizPage> {
  int _currentIndex = 0;
  QuizMaster quiz = QuizMaster();
  String _correctAnswer = 'Choose your correct answer!';
  int _index = 0;
  void increment() {
    setState(() {
      _index = _index + 1;
    });
    if (_index == quiz.questionList.length + 1) {
      _index = 0;
    }
    if (_index == 0) {
      _correctAnswer = 'Choose your correct answer!';
    } else if (_index == 1) {
      _correctAnswer = 'Synonym of Mendacity was: Falsehood';
    } else if (_index == 2) {
      _correctAnswer = 'Synonym of Culpable was: Guilty';
    } else {
      _index = 0;
      _correctAnswer = 'Choose your correct answer!';
      Alert(
        context: context,
        title: 'Quiz Completed.',
        desc: 'We\'ve reached the end. Thanks for taking part. Meet you again.',
      ).show();
    }
  }

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    final textTheme = Theme.of(context).textTheme;
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Playxis - Play + Lexis',
          style: QuizTheme.appbarStyle,
        ),
        backgroundColor: QuizTheme.shrinePink300,
      ),
      body: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Question(questions: quiz.questionList, index: _index),
            ...(quiz.questionList[_index]['answer'] as List<String>)
                .map((answer) {
              return Answer(answer: answer, pointToOnPress: increment);
            }).toList(),
            Container(
              padding: const EdgeInsets.all(5.0),
              width: 250.0,
              child: const Divider(
                thickness: 5.0,
                color: QuizTheme.shrinePink400,
              ),
            ),
            Container(
              width: double.infinity,
              alignment: Alignment.topCenter,
              margin: const EdgeInsets.only(
                left: 10.0,
                right: 10.0,
              ),
              child: Text(
                _correctAnswer,
                style: QuizTheme.questionStyle,
              ),
            ),
          ],
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        currentIndex: _currentIndex,
        backgroundColor: colorScheme.surface,
        selectedItemColor: colorScheme.onSurface,
        unselectedItemColor: colorScheme.onSurface.withOpacity(.60),
        selectedLabelStyle: textTheme.caption,
        unselectedLabelStyle: textTheme.caption,
        onTap: (value) {
          // Respond to item press.
          setState(
            () => _currentIndex = value,
          );
        },
        items: const [
          BottomNavigationBarItem(
            label: 'Favorites',
            icon: Icon(Icons.favorite),
          ),
          BottomNavigationBarItem(
            label: 'Music',
            icon: Icon(Icons.music_note),
          ),
          BottomNavigationBarItem(
            label: 'Places',
            icon: Icon(Icons.location_on),
          ),
          BottomNavigationBarItem(
            label: 'News',
            icon: Icon(Icons.library_books),
          ),
        ],
      ),
    );
  }
}

In some place we have also used the modified custom theme class.

In some place, we have used the constant Color variables that are static in nature. As a result we can access those constant Color properties through class name.

In the code above, we have seen a few examples like the following.

color: QuizTheme.shrinePink400,
...
backgroundColor: QuizTheme.shrinePink300,

After changing the custom theme from dark to light, we can also add some more pages. Aside from this, we will learn how to navigate to another page.

In the next section, we learn them. Before that, if you want to clone the newly modified Quiz App, please use this GitHub repository.

So stay tuned and happy Fluttering.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

Courses at Educative

GitHub repository

Technical blog

Twitter

Comments

3 responses to “How Flutter theme changes App design”

  1. […] First of all, we need to fill our ThemeData widget properties with custom colours. […]

  2. […] we will use it to pass a custom color scheme across the Flutter app. As a result we have to use the ValueNotifier class in accomplishing the […]

  3. […] Certainly we declared the theme in our Material App widget. […]

Leave a Reply