Implementing Android 12 Material You's Monet theme engine in Flutter
Tags: flutter, android, monet, tutorial
Published: 2022-03-14 15:30:53

## <u>Introduction to Material You</u>
The biggest change we saw in [**Android 12**](https://developer.android.com/about/versions/12) is **Material You**, which is the latest version of Google's Material design language. 
Google describes Material You as 

> "_seeks to create designs that are personal for every style, accessible for every need, alive and adaptive for every screen."_

During the development of Android 12, Google created a new theme engine code-named "**monet**" which generates a rich palette of pastel colors base on user's wallpaper. These colors are then applied to various parts of the system and their values are made available through an API that the user’s applications can call, thus letting apps decide whether they also want to recolor their UI. Google has been going all-in on Material You, and the company has updated most of its apps to incorporate dynamic colors. The source code of "monet" is now made available to [**AOSP**](https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-12.1.0_r1/packages/SystemUI/monet/) with the release of Android 12L. Previously the theme engine was Pixel exclusive.

**Note**: Monet wont't work on devices that are running android version which is older then 12.

![Monet Theme Engine Example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pepgkkhtv5lxybw4i8or.png)

Image Credits: [material.io](https://material.io/blog/start-buildinQg-with-material-you)

You can learn more Google's new Material You and dynamic theme-engine here:
- [Material You](https://www.androidpolice.com/2021/05/19/material-you-is-the-next-evolution-of-googles-design-language/)
- [Dynamic color](https://m3.material.io/styles/color/dynamic-color/overview)

---

## <u>Prerequisites</u>

- Basic Flutter knowledge
- Flutter 2.10.1 installed or later
- A physical device running Android 12 or new version of OS with monet support. (Android 12 emulator doesn't support monet as of now)

While writing this post, i am using a device with Android 12L Custom Rom installed which is based on AOSP.

As "monet" source code is recently open-sourced, devices other than Pixels will be getting Android 12 with dyanamic theming support later this year.

---

## <u>Final result</u>

In this tutorial we will be building a simple ID card application with Text() and FloatingActionButton() widgets to demonstrate implementation of monet.

Source code of this project is available here: [GitHub](https://github.com/vinaytiparadi/id_card_monet)

<u>Screenshots </u>

![app screenshot 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/opl1zfowmdw10w38hdcm.png)


![app screenshot 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/34jhbejqiz0yi4auy0sa.png)


![app screenshot 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvb5ebdostrd4t4yhks1.png)

If you installed the app on Android OS < Android 12 then the app will look like this:

![app screenshot 4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4f2p7y31c9j293uf1m1b.png)

---

## Let's get started!

- Create the Flutter app

Open your Android Studio and create new Flutter project as usual or if you prefer command line then run the command given below in terminal (for vscode users)

```dart
flutter create id_card_monet
```

- Add dependendies

Open pubspec.yaml file which will be available inside your project folder. In this case `id_card_monet/pubspec.yaml`

We will be using "dynamic_color" package which is supported on flutter 2.10.1 and later.

`dynamic_color` package is published by material.io and it's available [here](https://pub.dev/packages/dynamic_color).

Add `dynamic_color: ^1.1.2` in dependencies section of pubspec.yaml file.

Your dependencies section shoud look like this:

```dart
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  dynamic_color: ^1.1.2
```
After making changes to pubspec.yaml file, your next is to click on the popup saying `Pub get` on android studio or enter `flutter pub get` in terminal if you are using vscode. This will download all the required dependencies of your project.

Link to [dynamic_color](https://pub.dev/packages/dynamic_color) flutter package. 

- Import required dart package in main.dart

Navigate to `id_card_monet/lib/main.dart` file in your project and add this import `import 'package:dynamic_color/dynamic_color.dart';` so that you will be able to use the widgets from dynamic_color.dart file.

- Deleting the template code from main.dart

In the main.dart file you will see the template code provided by Flutter, we will write our custom widget tree so it's better to delete it.

After deleting the template code add the following code block:

```dart
void main() => runApp(const IdCard());
```
If you getting errors like IdCard() function is not defined, just follow the tutorial and you will be good to go!

- Creating a StatefulWidget

In main.dart file type `stful` and your IDE or editor will create a StatefulWidget and enter name of the StatefulWidget as 'IdCard'

If above method doesn't work for you, just copy paste from given code below:

```dart
class IdCard extends StatefulWidget {
  const IdCard({Key? key}) : super(key: key);

  @override
  State<IdCard> createState() => _IdCardState();
}

class _IdCardState extends State<IdCard> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
```

We are using a StatefulWidget as state of one of our widget changes.

- Use DynamicColorBuilder

Currently your _IdCardState looks like this

```dart
class _IdCardState extends State<IdCard> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
```
Replace above code snippet with 

```dart
class _IdCardState extends State<IdCard> {
  int age = 0;

  @override
  Widget build(BuildContext context) {
    return DynamicColorBuilder(
      builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
        return MaterialApp();
      },
    );
  }
}
```
We are returning DynamicColorBuilder() instead of Container().
`DynamicColorBuilder` is a stateful widget that provides the device's dynamic colors in a light and dark ColorScheme which are extracted from your wallpaper. Under the hood, `DynamicColorBuilder` uses a plugin to talk to the Android OS.

It builds the child widget of this widget, providing a light and dark ColorScheme.
The ColorSchemes will be null if dynamic color is not supported (i.e on non-Android platforms and pre-Android S devices), or if the colors have yet to be obtained.

- Customizing Scffold and AppBar with dynamic colors

Replace _IdCardState with: 

```dart
class _IdCardState extends State<IdCard> {
  int age = 0;

  @override
  Widget build(BuildContext context) {
    return DynamicColorBuilder(
      builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
        return MaterialApp(
          home: Scaffold(
            backgroundColor: darkDynamic?.background ?? Colors.white, // *1
            appBar: AppBar(
              title: Text(
                'ID Card',
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  letterSpacing: 1.9,
                  color: darkDynamic?.onPrimaryContainer ?? Colors.white, // *2
                ),
              ),
              centerTitle: true,
              backgroundColor:
                  darkDynamic?.secondaryContainer.withAlpha(50) ?? Colors.blue, // *3
              elevation: 6.0,
            ),
            floatingActionButton: FloatingActionButton(
              onPressed: () {
                setState(() {
                  age += 1;
                });
              },
              child: const Icon(
                Icons.add,
                color: Colors.black,
              ),
              backgroundColor: darkDynamic?.primary.withOpacity(1) ?? Colors.blue, // *4
            ),
            body: 
          ),
        );
      },
    );
  }
}


```
As we discussed we are getting `lightDynamic` and `darkDynamic` palette. We can use either of it or switch between them as Android OS switches from light mode to dark mode. In this app we are using `darkDynamic` palette.

Both the palette provides a lot of different colors which you can use in your app.

<u>Some of them are</u>: 
1. primary
2. tertiary
3. secondaryContainer
4. secondary
5. onPrimary
6. background 
7. onPrimaryContainer and much more

You can access them via the $colorPaletteName and . (dot) operator
For eg. `darkDynamic?.primary`

You can learn about the specific use of each color and customizing your app from [material.io](https://m3.material.io/foundations/customization) 

Now, coming to the code part
Refer comments from above snippet to understand which line i am talking about.

*1 `backgroundColor: darkDynamic?.background ?? Colors.white,`

For the backgroundColor of our Scaffold Widget we are using darkDynamic palette and background color extracted from our wallpaper. If the use is using Android OS older than Android 12 (S) or for some reasons it doesn't get dynamic palette, the scaffold background will be set 'white' color from material library.

*2 `color: darkDynamic?.onPrimaryContainer ?? Colors.white,`

This sets the color of AppBar title to onPrimary Container extracted from wallpaper and if dynamicPalette returns null, then AppBar title color is set to empty.

*3 `darkDynamic?.secondaryContainer.withAlpha(50) ?? Colors.blue,`

This is similar to above implementation but if you notice I have added alpha to the extracted secondaryContainer color. You can tweak the colors according to you.

There are several methods you can call and tweak colors:

1. withOpacity(double opacity)
2. withAlpha(int a)
3. withGreen(int a)
4. withRed(int a)
5. withBlue(int a)
6. harmonizeWith(Color color)

`harmonizeWith` method can be used to shift the hue of the color towards the passed in color

*4 `backgroundColor: darkDynamic?.primary.withOpacity(1) ?? Colors.blue,`

Here I have set the opacity to 1 which is the maximum opacity which you can pass and it's the default value. 

- Adding other widgets to our app

Replace the _IdCardState with code given below to complete our app ui.

```dart
class _IdCardState extends State<IdCard> {
  int age = 0;

  @override
  Widget build(BuildContext context) {
    return DynamicColorBuilder(
      builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
        return MaterialApp(
          home: Scaffold(
            backgroundColor: darkDynamic?.background ?? Colors.white, // *1
            appBar: AppBar(
              title: Text(
                'ID Card',
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  letterSpacing: 1.9,
                  color: darkDynamic?.onPrimaryContainer ?? Colors.white, // *2
                ),
              ),
              centerTitle: true,
              backgroundColor:
                  darkDynamic?.secondaryContainer.withAlpha(50) ?? Colors.blue, // *3
              elevation: 6.0,
            ),
            floatingActionButton: FloatingActionButton(
              onPressed: () {
                setState(() {
                  age += 1;
                });
              },
              child: const Icon(
                Icons.add,
                color: Colors.black,
              ),
              backgroundColor: darkDynamic?.primary.withOpacity(1) ?? Colors.blue, // *4
            ),
            body: Padding(
              padding: const EdgeInsets.fromLTRB(30, 40, 30, 30),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisAlignment: MainAxisAlignment.start,
                children: <Widget>[
                  Text(
                    'NAME',
                    style: TextStyle(
                      color:
                          darkDynamic?.primary.withOpacity(0.9) ?? Colors.black, // *5
                      letterSpacing: 2,
                    ),
                  ),
                  const SizedBox(height: 10),
                  Text(
                    'Vinay :)',
                    style: TextStyle(
                      color: darkDynamic?.secondary ?? Colors.blue, // *6
                      letterSpacing: 2,
                      fontSize: 28.0,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 30),
                  Text(
                    'CURRENT AGE',
                    style: TextStyle(
                      color:
                          darkDynamic?.primary.withOpacity(0.9) ?? Colors.black, // *7
                      letterSpacing: 2,
                    ),
                  ),
                  const SizedBox(height: 10),
                  Text(
                    '$age',
                    style: TextStyle(
                      color: darkDynamic?.secondary ?? Colors.blue, // *8
                      letterSpacing: 2,
                      fontSize: 28.0,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 30),
                  Text(
                    'CONTACT',
                    style: TextStyle(
                      color:
                          darkDynamic?.primary.withOpacity(0.9) ?? Colors.black, // *9
                      letterSpacing: 2,
                    ),
                  ),
                  const SizedBox(
                    height: 10,
                  ),
                  Row(
                    children: <Widget>[
                      Icon(
                        Icons.email,
                        color: darkDynamic?.secondary ?? Colors.blue, // *10
                      ),
                      const SizedBox(width: 10),
                      Text(
                        'xyz@gmail.com',
                        style: TextStyle(
                          color: darkDynamic?.secondary ?? Colors.blue, // *11
                          letterSpacing: 1,
                          fontSize: 18,
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        );
      },
    );
  }
}



```
I have used Text(), SizedBox() and Icon() widgets to complete the UI. 

*5, *6, *7, *8, *9, *10 and *11 uses the same logic as explained before to implements dynamic theming to your app. 

- Complete main.dart code

```dart
import 'package:flutter/material.dart';
import 'package:dynamic_color/dynamic_color.dart';

void main() => runApp(const IdCard());

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

  @override
  State<IdCard> createState() => _IdCardState();
}

class _IdCardState extends State<IdCard> {
  int age = 0;

  @override
  Widget build(BuildContext context) {
    return DynamicColorBuilder(
      builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
        return MaterialApp(
          home: Scaffold(
            backgroundColor: darkDynamic?.background ?? Colors.white, // *1
            appBar: AppBar(
              title: Text(
                'ID Card',
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  letterSpacing: 1.9,
                  color: darkDynamic?.onPrimaryContainer ?? Colors.white, // *2
                ),
              ),
              centerTitle: true,
              backgroundColor: darkDynamic?.secondaryContainer.withAlpha(50) ??
                  Colors.blue, // *3
              elevation: 6.0,
            ),
            floatingActionButton: FloatingActionButton(
              onPressed: () {
                setState(() {
                  age += 1;
                });
              },
              child: const Icon(
                Icons.add,
                color: Colors.black,
              ),
              backgroundColor:
                  darkDynamic?.primary.withOpacity(1) ?? Colors.blue, // *4
            ),
            body: Padding(
              padding: const EdgeInsets.fromLTRB(30, 40, 30, 30),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisAlignment: MainAxisAlignment.start,
                children: <Widget>[
                  Text(
                    'NAME',
                    style: TextStyle(
                      color: darkDynamic?.primary.withOpacity(0.9) ??
                          Colors.black, // *5
                      letterSpacing: 2,
                    ),
                  ),
                  const SizedBox(height: 10),
                  Text(
                    'Vinay :)',
                    style: TextStyle(
                      color: darkDynamic?.secondary ?? Colors.blue, // *6
                      letterSpacing: 2,
                      fontSize: 28.0,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 30),
                  Text(
                    'CURRENT AGE',
                    style: TextStyle(
                      color: darkDynamic?.primary.withOpacity(0.9) ??
                          Colors.black, // *7
                      letterSpacing: 2,
                    ),
                  ),
                  const SizedBox(height: 10),
                  Text(
                    '$age',
                    style: TextStyle(
                      color: darkDynamic?.secondary ?? Colors.blue, // *8
                      letterSpacing: 2,
                      fontSize: 28.0,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 30),
                  Text(
                    'CONTACT',
                    style: TextStyle(
                      color: darkDynamic?.primary.withOpacity(0.9) ??
                          Colors.black, // *9
                      letterSpacing: 2,
                    ),
                  ),
                  const SizedBox(
                    height: 10,
                  ),
                  Row(
                    children: <Widget>[
                      Icon(
                        Icons.email,
                        color: darkDynamic?.secondary ?? Colors.blue, // *10
                      ),
                      const SizedBox(width: 10),
                      Text(
                        'xyz@gmail.com',
                        style: TextStyle(
                          color: darkDynamic?.secondary ?? Colors.blue, // *11
                          letterSpacing: 1,
                          fontSize: 18,
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        );
      },
    );
  }
}

```
If you made it till here then congrats! 

Last step is to test your app with dyanamic theme on your device 📱

Visit this project on [GitHub](https://github.com/vinaytiparadi/id_card_monet) and ⭐ it so that you can refer it anytime. 

Credits: 
[Abhishek Pandey](https://github.com/AbhiShake1) for helping me.