Dynamic Theming in Flutter: Time-Based UI Adaptation

Maxim Gorin
4 min readNov 12, 2024

--

Welcome to another part of our Flutter series! In our previous article, The Power of Touch in Flutter: A Guide to Gesture Handling, we focused on managing user interactions. Now, let’s explore a more visually engaging topic: creating a dynamic, adaptive theme in Flutter based on the time of day. This feature can enhance user experience by changing the look and feel of your app to suit different times of the day — morning, afternoon, or evening.

Dynamic Theming in Flutter: Time-Based UI Adaptation

In this article, we will:

  1. Define custom themes for different times of the day.
  2. Implement automatic theme changes based on the current time.
  3. Add a user-friendly slider for manual theme selection.
  4. Explain key concepts and provide a self-contained code that you can copy, paste, and run directly.

Let’s dive in!

Setting Up Your Project

Before we start, make sure your project has the necessary imports at the top. This is crucial for using Flutter’s widgets and handling timers.

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

void main() => runApp(MyApp());

Explanation

  • import 'dart:async': Provides access to Dart's Timer class, which we will use to update the theme periodically.
  • import 'package:flutter/material.dart': Includes the Flutter framework’s core widgets and utilities.
  • main() function: Initializes the app by running MyApp.

Creating Custom Themes

We start by defining separate themes for morning, afternoon, and evening. Each theme will have its own color palette and style to match the time of day.

final ThemeData morningTheme = ThemeData(
brightness: Brightness.light,
primaryColor: Colors.blue,
scaffoldBackgroundColor: Colors.blue[50],
textTheme: TextTheme(
bodyLarge: TextStyle(color: Colors.blue[800], fontSize: 18),
),
);

final ThemeData dayTheme = ThemeData(
brightness: Brightness.light,
primaryColor: Colors.orangeAccent,
scaffoldBackgroundColor: Colors.orange[50],
textTheme: TextTheme(
bodyLarge: TextStyle(color: Colors.orange[200], fontSize: 18),
),
);

final ThemeData nightTheme = ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.indigo,
scaffoldBackgroundColor: Colors.indigo[900],
textTheme: TextTheme(
bodyLarge: TextStyle(color: Colors.white, fontSize: 18),
),
);

Explanation

  • Morning Theme: Uses shades of blue to reflect the freshness of the early hours.
  • Afternoon Theme: Applies bright orange colors to capture the warmth and energy of midday.
  • Evening Theme: Features darker indigo tones for a relaxing, night-time experience.

By defining these themes separately, we can easily switch between them based on the time of day.

Using Enums for Theme Modes

Dark Mode | Apple Developer Documentation

To manage different time-based themes, we define an enum called ThemeMode. This approach simplifies the code and makes it more maintainable by avoiding string comparisons.

enum ThemeMode {
auto,
morning,
afternoon,
evening;

ThemeData get theme {
switch (this) {
case ThemeMode.morning:
return morningTheme;
case ThemeMode.afternoon:
return dayTheme;
case ThemeMode.evening:
return nightTheme;
case ThemeMode.auto:
return _currentThemeMode.theme;
}
}

String get greeting {
switch (this) {
case ThemeMode.morning:
return "Good Morning";
case ThemeMode.afternoon:
return "Good Afternoon";
case ThemeMode.evening:
return "Good Evening";
case ThemeMode.auto:
return _currentThemeMode.greeting;
}
}

String get emoji {
switch (this) {
case ThemeMode.morning:
return "🌅";
case ThemeMode.afternoon:
return "☀️";
case ThemeMode.evening:
return "🌃";
case ThemeMode.auto:
return _currentThemeMode.emoji;
}
}

List<Color> get gradientColors {
switch (this) {
case ThemeMode.morning:
return [Colors.lightBlueAccent, Colors.blue];
case ThemeMode.afternoon:
return [Colors.orangeAccent, Colors.deepOrange];
case ThemeMode.evening:
return [Colors.indigo, Colors.black87];
case ThemeMode.auto:
return _currentThemeMode.gradientColors;
}
}

static ThemeMode get _currentThemeMode {
final hour = DateTime.now().hour;
if (hour >= 5 && hour < 12) return ThemeMode.morning;
if (hour >= 12 && hour < 17) return ThemeMode.afternoon;
return ThemeMode.evening;
}
}

Explanation

  • Auto Mode: Automatically adjusts the theme based on the current time.
  • Enums: Provide a type-safe way to handle theme modes, reducing errors.

Managing State and Setting Up the Timer

Styles and themes | Views | Android Developers

To manage the theme updates, we use a StatefulWidget and a Timer that checks the time every minute.

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
ThemeMode _selectedMode = ThemeMode.auto;
Timer? _timer;

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

@override
void dispose() {
_timer?.cancel();
super.dispose();
}

void _startThemeUpdateTimer() {
_timer = Timer.periodic(Duration(minutes: 1), (timer) {
if (_selectedMode == ThemeMode.auto) {
setState(() {});
}
});
}

void _updateTheme(ThemeMode mode) {
setState(() {
_selectedMode = mode;
});
}
}

Explanation

  • initState: Initializes the timer when the app starts.
  • dispose: Cancels the timer when the app is closed to avoid memory leaks.
  • Timer: Checks the time every minute and updates the theme if necessary.

Building the UI with Animated Transitions

We use an AnimatedSwitcher for smooth transitions between themes.

@override
Widget build(BuildContext context) {
final currentTheme = _selectedMode.theme;
final greetingMessage = _selectedMode.greeting;
final emoji = _selectedMode.emoji;

return MaterialApp(
theme: currentTheme,
home: AnimatedSwitcher(
duration: Duration(seconds: 1),
child: Scaffold(
key: ValueKey(currentTheme),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: _selectedMode.gradientColors,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Spacer(),
Text(emoji, style: TextStyle(fontSize: 100)),
SizedBox(height: 20),
Text(greetingMessage, style: TextStyle(fontSize: 30)),
Spacer(),
_buildModeSlider(),
SizedBox(height: 40),
],
),
),
),
),
);
}

Creating a Mode Selection Slider

Widget _buildModeSlider() {
return SliderTheme(
data: SliderThemeData(
thumbColor: _selectedMode.theme.primaryColor,
activeTrackColor: _selectedMode.theme.primaryColor,
),
child: Slider(
value: _selectedMode.index.toDouble(),
min: 0,
max: 3,
divisions: 3,
onChanged: (value) {
_updateTheme(ThemeMode.values[value.toInt()]);
},
label: _selectedMode.name,
),
);
}

Conclusion

In this article, we built a dynamic theming system in Flutter that adapts to the time of day. This technique adds a polished, responsive feel to your app. By using enums, timers, and smooth transitions, we created an adaptive UI that enhances user experience.

Try implementing this feature in your projects and share your feedback. Keep exploring, experimenting, and building amazing Flutter apps!

Happy coding! 🌅☀️🌃

--

--

Maxim Gorin
Maxim Gorin

Written by Maxim Gorin

Team lead in mobile development with a passion for Fintech and Flutter. Sharing insights and stories from the tech and dev world on this blog.

No responses yet