In this guide, you will learn how to create a Flutter application that allows users to input timer values (hours, minutes, and seconds) on one page and see a live countdown on the next page. When the timer reaches 0:00:00, an audio notification plays, alerting the user. This multi-page setup uses Flutter’s Navigator for screen transitions and Timer.periodic to manage countdown scheduling. Additionally, the sound functionality is implemented using a well-known Flutter audio playback package.
To begin, ensure you have created a Flutter project. In your project’s pubspec.yaml file, add the dependencies required for audio playback and optionally for local notifications or other features as needed. For our implementation, we will utilize the audioplayers package.
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
audioplayers: ^5.2.0
flutter:
assets:
- assets/alarm_sound.mp3
Make sure you have an alarm sound file (for example, alarm_sound.mp3) placed inside an assets folder at the root of your project. This file will play when the timer reaches zero.
The Timer Input Page is the starting screen that lets users input the desired duration for the countdown. It features three inputs for hours, minutes, and seconds, designed using TextFormField widgets for validation and proper input handling. Once valid input is provided, the user can navigate to the Countdown Timer Page by tapping the "Start Timer" button.
/* TimerInputPage.dart */
import 'package:flutter/material.dart';
import 'timer_display_page.dart';
class TimerInputPage extends StatefulWidget {
@override
_TimerInputPageState createState() => _TimerInputPageState();
}
class _TimerInputPageState extends State<TimerInputPage> {
final _formKey = GlobalKey<FormState>();
int _hours = 0;
int _minutes = 0;
int _seconds = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Set Timer')),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Hours'),
keyboardType: TextInputType.number,
validator: (value) {
if(value == null || value.isEmpty) return 'Please enter hours';
int val = int.tryParse(value)!;
if(val < 0 || val > 23) return 'Hours must be between 0 and 23';
return null;
},
onSaved: (value) => _hours = int.parse(value!),
),
TextFormField(
decoration: InputDecoration(labelText: 'Minutes'),
keyboardType: TextInputType.number,
validator: (value) {
if(value == null || value.isEmpty) return 'Please enter minutes';
int val = int.tryParse(value)!;
if(val < 0 || val > 59) return 'Minutes must be between 0 and 59';
return null;
},
onSaved: (value) => _minutes = int.parse(value!),
),
TextFormField(
decoration: InputDecoration(labelText: 'Seconds'),
keyboardType: TextInputType.number,
validator: (value) {
if(value == null || value.isEmpty) return 'Please enter seconds';
int val = int.tryParse(value)!;
if(val < 0 || val > 59) return 'Seconds must be between 0 and 59';
return null;
},
onSaved: (value) => _seconds = int.parse(value!),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if(_formKey.currentState!.validate()){
_formKey.currentState!.save();
// Calculate total seconds
int totalSeconds = _hours * 3600 + _minutes * 60 + _seconds;
if(totalSeconds > 0) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TimerDisplayPage(totalSeconds: totalSeconds),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please set a valid timer')),
);
}
}
},
child: Text('Start Timer'),
)
],
),
),
),
);
}
}
This page ensures that each input is validated and restricts the input ranges to maintain valid time values. The Navigator is used to pass user input to the next page.
The Countdown Timer Page handles the timer functionality. When this page is loaded, it calculates the total remaining seconds, starts a periodic timer (using Timer.periodic), and continuously updates the display. A conditional check stops the timer when the counter reaches zero. At that point, the sound functionality is triggered to play an alarm.
/* TimerDisplayPage.dart */
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
class TimerDisplayPage extends StatefulWidget {
final int totalSeconds;
TimerDisplayPage({required this.totalSeconds});
@override
_TimerDisplayPageState createState() => _TimerDisplayPageState();
}
class _TimerDisplayPageState extends State<TimerDisplayPage> {
late int _remainingSeconds;
Timer? _timer;
final AudioPlayer _audioPlayer = AudioPlayer();
@override
void initState() {
super.initState();
_remainingSeconds = widget.totalSeconds;
_startTimer();
}
void _startTimer() {
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {
if(_remainingSeconds > 0) {
_remainingSeconds--;
} else {
timer.cancel();
_playAlarmSound();
}
});
});
}
Future<void> _playAlarmSound() async {
await _audioPlayer.play(AssetSource('assets/alarm_sound.mp3'));
}
String _formatTime(int seconds) {
int hours = seconds ~/ 3600;
int minutes = (seconds % 3600) ~/ 60;
int secs = seconds % 60;
return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}';
}
@override
void dispose() {
_timer?.cancel();
_audioPlayer.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Countdown Timer')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_formatTime(_remainingSeconds),
style: TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Cancel Timer'),
),
],
),
),
);
}
}
In this part, the Timer.periodic function is used to decrement the counter each second. Once the timer reaches zero, the code stops the timer and calls the function to play the alarm sound. The audio player is configured to play the local asset sound, ensuring immediate feedback upon completion of the countdown.
The main function brings everything together by making the Timer Input Page the starting point of the application. This is a simple setup, where the MaterialApp widget encapsulates all the configurations and routes.
/* main.dart */
import 'package:flutter/material.dart';
import 'timer_input_page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Timer App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: TimerInputPage(),
);
}
}
To improve the user experience, always include intuitive error messages and input validation on your Timer Input Page. For instance, if a user enters a negative time value or leaves a field empty, provide immediate feedback using SnackBar messages. You can also enhance aesthetics by applying custom themes and custom styles to buttons and text.
Component | Purpose |
---|---|
TextFormField | User input for hours, minutes, and seconds; includes validation |
ElevatedButton | Navigates from the input screen to the countdown timer screen |
Timer.periodic | Decrements the countdown value in real-time |
AudioPlayer | Plays the alarm sound when the timer hits zero |
Navigator | Manages navigation between input and display pages |
The architecture of the Timer App is purposely designed to keep the code modular and maintainable. The separation into distinct pages not only provides a clean user experience but also helps in isolating the timer logic from the UI logic. By properly managing state and using the dispose methods to cancel timers and dispose audio players when not needed anymore, the app ensures optimal resource usage.
Furthermore, the use of packages such as audioplayers has become a standard approach for handling audio in Flutter applications, offering ease of integration and customization. Additionally, Flutter's built-in Navigator widget simplifies page transitions and data passing, making the overall structure both scalable and easy to modify.
Developers can also expand this basic implementation by adding features such as pause/resume functionality, multiple alarms, and even visual notifications using local notifications. For enhanced debugging, consider adding comprehensive error handling and logging mechanisms.
Moreover, by leveraging Flutter’s hot reload and the extensive widget library, developers can experiment with various designs and optimizations, ensuring that both functionality and aesthetics meet application requirements. This guide provides a flexible foundation, which can be further evolved to support more complex timer functionalities, like countdown animations or background execution.
In summary, this guide has outlined how to build a comprehensive multi-page Flutter timer application. Starting with a well-validated input page, it seamlessly transitions to a countdown timer page that updates every second and plays an alarm sound when time expires. By following the modular code structure provided, you ensure that the application is both scalable and maintainable. The implementation leverages Flutter's core features and popular packages to deliver a robust user experience.
The steps include setting up the environment, configuring dependencies, and implementing both UI and backend logic for timer management and audio playback. With this foundation, you can expand further into more advanced functionalities such as custom animations, improved user interaction, and notifications. Overall, this solution demonstrates how Flutter’s flexible framework can be used to create practical and engaging applications.