A stopwatch application built with Flutter, following clean architecture principles. This project demonstrates state management using the BLoC library, testing, and modular design for scalable Flutter apps.
The project template was generated with very_good_cli (v0.27.0) and was developed on Flutter 3.35.3.
- Stopwatch Functionality: Start, pause, stop, and record laps with millisecond precision.
- Multiple Clock Displays: Digital and analog clock widgets for flexible UI.
- State Management: Bloc pattern.
- Clean Architecture: Separation of data, domain, and presentation layers.
- Testing:
- Unit, widget, and integration tests
- Custom robots and fakes for UI and business logic coverage
- Platform Support: Android, iOS, Web, macOS, Windows
The packages/
directory contains shared Flutter package(s) for modularity and code reuse. Each package is intended to be reused across multiple apps inside an organization - either in a monorepo
setup like in this project - or published separately
for easy dependency handling.
- utils/: Common utilities, constants, extensions, and error handling used across multiple apps and features.
-
Persistence (Planned): The architecture is prepared for future support of persistent stopwatch state, enabling the stopwatch to continue running in the background and survive app restarts.
⚠️ This feature is not yet implemented. The codebase is structured for easy extension to background and persistent operation, but persistence is currently missing.
- The project follows a feature-first clean architecture style
lib/
app/ # App bootstrap, theming, navigation
exceptions/ # Error handling
features/
stopwatch/ # Stopwatch feature
data/ # Data sources, models, repositories
domain/ # Entities, repositories, services, use cases
presentation/ # Bloc, widgets, pages
routing/ # Routing configuration
l10n/ # Localization files
integration_test/ # End-to-end and workflow tests
app_robot.dart # Robot for UI automation
app_test.dart # Main integration test
workflows.dart # Complex workflow coverage
test/ # Unit and widget tests
feature/ # Feature-specific tests
helpers/ # Test helpers, fakes, robots
packages/
utils/ # Common utilities, extensions, error handling.
- Clone the repository
git clone https://github.com/ZsoltFischer/Flutter-Stopwatch.git cd Flutter-Stopwatch
- Install dependencies
flutter pub get
- Run the app This project contains 3 flavors:
- development
- staging
- production
To run the desired flavor either use the launch configuratiions in VSCode or run the appropriate command:
# Development
$ flutter run --flavor development --target lib/main_development.dart
# Staging
$ flutter run --flavor staging --target lib/main_staging.dart
# Production
$ flutter run --flavor production --target lib/main_production.dart
- Run tests Unit/Widget tests:
$ flutter test
or
$ very_good test --coverage --test-randomize-ordering-seed random
Integration tests (requires emulator/device):
$ flutter test integration_test --flavor <flavor> integration_test/app_test.dart
To view the generated coverage report you can use lcov.
# Generate Coverage Report
$ genhtml coverage/lcov.info -o coverage/
# Open Coverage Report
$ open coverage/index.html
- Unit Tests: Validate business logic and state transitions.
- Widget Tests: Verify UI rendering and interaction in
test/feature/stopwatch/presentation/widgets/
. - Integration Tests: Use
integration_test/app_robot.dart
for complex workflows and end-to-end scenarios. ⚠️ Golden Tests: On the roadmap. Verify individual components like the analog clock face- Test Helpers: Custom fakes, robots, and pump helpers for maintainable tests.
- Feature-first -- Data Layer: Handles data sources, models, and repository implementations. -- Domain Layer: Contains entities, repository interfaces, services, and use cases. -- Presentation Layer: Manages UI, Bloc state, and widget composition.
This project relies on flutter_localizations and follows the official internationalization guide for Flutter.
- To add a new localizable string, open the
app_en.arb
file atlib/l10n/arb/app_en.arb
.
{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
}
}
- Then add a new key/value and description
{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
},
"helloWorld": "Hello World",
"@helloWorld": {
"description": "Hello World Text"
}
}
- Use the new string
import 'package:stopwatch/l10n/l10n.dart';
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Text(l10n.helloWorld);
}
Update the CFBundleLocalizations
array in the Info.plist
at ios/Runner/Info.plist
to include the new locale.
...
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>es</string>
</array>
...
- For each supported locale, add a new ARB file in
lib/l10n/arb
.
├── l10n
│ ├── arb
│ │ ├── app_en.arb
│ │ └── app_es.arb
- Add the translated strings to each
.arb
file:
app_en.arb
{
"@@locale": "en",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
}
}
app_es.arb
{
"@@locale": "es",
"counterAppBarTitle": "Contador",
"@counterAppBarTitle": {
"description": "Texto mostrado en la AppBar de la página del contador"
}
}
To use the latest translations changes, you will need to generate them:
- Generate localizations for the current project:
flutter gen-l10n --arb-dir="lib/l10n/arb"
Alternatively, run flutter run
and code generation will take place automatically.
For more details, see the source code and tests in the repository.