Flutter themes — Light and Dark mode iOS/Android

Connel Asikong
Nerd For Tech
Published in
7 min readFeb 12, 2023

It’s been awhile since you heard from me… but right now we are going to be looking at how to Theme our Apps.
As you know, starting from Android 10 and iOS 13, your devices can now do Dark and Light modes. So you have to have that in mind when creating your apps in other for it to work perfectly. Also take into consideration your splash screen and your images.

Photo by Isabella and Zsa Fischer on Unsplash

Let’s start by creating a new Flutter project, if you don’t know how then visit here.
Remove the default counter app that comes with new Flutter App.
We’re going to use Flutter splash screen package. The instructions on how to use it is there, you can even watch a video on youtube. Open pubspec.yaml file and add the package flutter_native_splash: ^2.2.17 then run pub get and before you continue add your assets/images, most people create images for both Light and Dark modes but I downloaded mine from icons8.

flutter_native_splash:
color: "#ffffff"
color_dark: "#000000"
image: assets/images/logo_black.png
image_dark: assets/images/logo_white.png

Add this to your yaml file, so for light theme, background color will be white and for dark theme, it will be black. Then I have images for both modes, now run this:

flutter pub run flutter_native_splash:create

Note: run this command every time you change the image or color to rebuild. Now lets run the app

Look at our splash screen for both Dark and Light modes. Isn’t it lovely? Yes!
BTW, If you’re an Anime fan you’d know that One Piece is the G.O.A.T (if you think otherwise drop your location let’s settle this!!) and Bleach TYBW has been crazy… now you can shout BANKAI!!!

I feel your rage! 😂🤣
Now let’s continue. I’m going to do a simple intro screen and I’m not going to spend much in explaining it maybe I’ll do another write up for that. Create a new file called onboarding_widget.dart and create a stateless widget

import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';

class OnboardingPageView extends StatelessWidget {
final image;
final title;
final Widget child1;
final Widget child2;

const OnboardingPageView({
Key? key,
this.image,
this.title,
required this.child1,
required this.child2,
}) : super(key: key);

@override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return SizedBox(
width: size.width,
height: size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
width: size.width,
child: SvgPicture.asset(
image,
fit: BoxFit.cover,
),
),
Flexible(child: Container()),
Center(
child: Text(
title,
style: Theme.of(context).textTheme.headline1,
textAlign: TextAlign.center,
),
),
Flexible(child: Container()),
child1,
Flexible(child: Container()),
child2,
Flexible(child: Container()),
],
),
);
}
}

This Widget file is the structure of how our Intro screens will look like.
Now let’s fix up our theme.

Customized Themes

theme property uses the ThemeData object, so we’re going to customize that and to do that I’m creating a file app_theme.dart with a class:

class AppTheme {
static const primary = Color(0xFF000000);
static const secondary = Color(0xFFFFFFFF);
static const greyColorShade1 = Color(0xFF777777);
static const greyColorShade2 = Color(0xFF6D6B6B);

static TextTheme whiteText = const TextTheme(
bodyText1: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w500,
color: Colors.white,
),
bodyText2: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w700,
color: Colors.white,
),
subtitle1: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w500,
color: Colors.white,
),
headline1: TextStyle(
fontSize: 35.0,
fontWeight: FontWeight.w600,
color: Colors.white,
letterSpacing: 0,
height: 1.12,
),
headline2: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.w700,
color: Colors.white,
),
headline3: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w700,
color: Colors.white,
),
headline4: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w600,
color: Colors.white,
),
headline5: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w600,
color: Colors.white,
),
headline6: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: Colors.white,
),
);

static TextTheme blackText = const TextTheme(
bodyText1: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w500,
color: Colors.black,
),
bodyText2: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: Colors.black,
),
subtitle1: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w500,
color: Colors.black,
),
headline1: TextStyle(
fontSize: 35.0,
fontWeight: FontWeight.w600,
color: Colors.black,
letterSpacing: 0,
height: 1.12,
),
headline2: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w700,
color: Colors.black,
),
headline3: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w700,
color: Colors.black,
),
headline4: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w700,
color: Colors.black,
),
headline5: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w700,
color: Colors.black,
),
headline6: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w700,
color: Colors.black,
),
);

static TextTheme greyText = const TextTheme(
bodyText1: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w500,
color: greyColorShade1,
),
bodyText2: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w700,
color: greyColorShade1,
),
subtitle1: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w500,
color: greyColorShade1,
),
headline1: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.w700,
color: greyColorShade1,
),
headline2: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w700,
color: greyColorShade1,
),
headline3: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w700,
color: greyColorShade1,
),
headline4: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w700,
color: greyColorShade1,
),
headline5: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w600,
color: greyColorShade1,
),
headline6: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w600,
color: greyColorShade1,
),
);

static TextTheme transText = const TextTheme(
bodyText1: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w500,
),
bodyText2: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w700,
),
subtitle1: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w500,
),
headline1: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.w700,
),
headline2: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w700,
),
headline3: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w700,
),
headline4: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w700,
),
headline5: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w600,
),
headline6: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w600,
),
);

static ThemeData dark() {
return ThemeData(
brightness: Brightness.dark,
fontFamily: 'Poppins',
primaryColor: secondary,
scaffoldBackgroundColor: primary,
appBarTheme: const AppBarTheme(
foregroundColor: secondary,
backgroundColor: primary,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: secondary,
foregroundColor: primary,
),
),
floatingActionButtonTheme: const FloatingActionButtonThemeData(
foregroundColor: primary,
backgroundColor: secondary,
),
textTheme: whiteText,
);
}

static ThemeData light() {
return ThemeData(
brightness: Brightness.light,
fontFamily: 'Poppins',
primaryColor: primary,
scaffoldBackgroundColor: secondary,
appBarTheme: const AppBarTheme(
foregroundColor: primary,
backgroundColor: secondary,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: primary,
foregroundColor: secondary,
),
),
floatingActionButtonTheme: const FloatingActionButtonThemeData(
foregroundColor: secondary,
backgroundColor: primary,
),
textTheme: blackText,
);
}
}

You can see that I’ve created a ThemeData of dark() and light() custom themes respectfully. You can see that I’ve defined TextTheme for whiteText (used for Dark mode), blackText (used for Light mode), greyText and transText(this doesn’t have color so it’s color can be influnced by the theme and BTW, it’s short for TransparentText).
scaffoldBackgroundColor will change the background color of the screen or your Scaffold Widget, appBarTheme will change the color of your AppBar etc.

Now I’m going to create our intro_screen.dart file but you’ll have to pub get this package then import our onboarding widget:

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

import 'onboarding_widget.dart';
import 'app_theme.dart';

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

@override
State<OnBoardingScreen> createState() => _OnBoardingScreenState();
}

class _OnBoardingScreenState extends State<OnBoardingScreen> {
final controller = PageController();
bool isDone = false;

@override
void initState() {
super.initState();
}

// handles going to the next page
void _onIntroNext() {
controller.nextPage(
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
controller.page == 1 ? (setState(() => {isDone = true})) : isDone = false;
}

// handles moving out the intro screen
void _onIntroEnd() {}

@override
void dispose() {
super.dispose();

controller.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: PageView(
controller: controller,
// physics: const NeverScrollableScrollPhysics(), uncomment this if you don't want dragging/sliding to/fro screens
children: [
OnboardingPageView(
image: "assets/images/intro01.svg",
title: "My name is Connel Asikong, connelblaze online",
child1: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: SmoothPageIndicator(
controller: controller, // PageController
count: 3,
effect: ExpandingDotsEffect(
spacing: 8.0,
radius: 4.0,
dotWidth: 8.0,
dotHeight: 6.0,
expansionFactor: 3,
dotColor: Colors.grey,
activeDotColor: Theme.of(context).primaryColor,
), // your preferred effect
onDotClicked: (index) {},
),
),
child2: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Row(
children: [
Expanded(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 10.0),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
onPressed: _onIntroNext,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
'Next',
style: AppTheme.transText.headline5,
),
),
),
),
),
],
),
),
),
OnboardingPageView(
image: "assets/images/intro02.svg",
title: "We've come to the end of this tutorial...",
child1: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: SmoothPageIndicator(
controller: controller, // PageController
count: 3,
effect: ExpandingDotsEffect(
spacing: 8.0,
radius: 4.0,
dotWidth: 8.0,
dotHeight: 6.0,
expansionFactor: 3,
dotColor: Colors.grey,
activeDotColor: Theme.of(context).primaryColor,
), // your preferred effect
onDotClicked: (index) {},
),
),
child2: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Row(
children: [
Expanded(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 10.0),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
onPressed: _onIntroNext,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
'Next',
style: AppTheme.transText.headline5,
),
),
),
),
),
],
),
),
),
OnboardingPageView(
image: "assets/images/intro06.svg",
title: "I hope you loved it. Please clap and share!",
child1: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: SmoothPageIndicator(
controller: controller, // PageController
count: 3,
effect: ExpandingDotsEffect(
spacing: 8.0,
radius: 4.0,
dotWidth: 8.0,
dotHeight: 6.0,
expansionFactor: 3,
dotColor: Colors.grey,
activeDotColor: Theme.of(context).primaryColor,
), // your preferred effect
onDotClicked: (index) {},
),
),
child2: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Row(
children: [
Expanded(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 10.0),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
onPressed: _onIntroEnd,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
'Get Started',
style: AppTheme.transText.headline5,
),
),
),
),
),
],
),
),
),
],
),
),
],
),
);
}
}

We should break this into more Widgets but I just want to wrap this up now go to main.dart and add your themes and widgets:

import 'package:flutter/material.dart';

import 'intro_screen.dart';
import 'app_theme.dart';

void main() {
runApp(const CB());
}

class CBextends StatelessWidget {
const CB({super.key});

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'CB App',
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
debugShowCheckedModeBanner: false,
home: const OnBoardingScreen(),
);
}
}

You can manually switch the themes in-app using this package and it’s that easy.
Check this out:

Anyway thank you for following me on this journey.
Please clap for me and you can clap multiple times
You can also check out my other write-ups.

You can also buy me coffee

Have a wonderful time yoi!

--

--

Connel Asikong
Nerd For Tech

Programmer, Designer, Promoter, music lover, God fearer. thrilled about technology. wish I could help and do more.