Note
Only here to translate? You don't need any of this. Head to TRANSLATING.md — no code required.
| Small fix | Just open a pull request. |
| Feature or behavior change | Open an issue first, so we agree on the direction before you spend an evening on it. |
| Bug | The issue templates ask for your app version and device — those two answers save all the guessing. |
Streak is deliberately small, offline and free of trackers. Analytics, ads, accounts and network backends are out of scope.
git clone https://github.com/InlitX/streak.git
cd streak
flutter pub get # also generates the localization code
flutter runYou'll need the Flutter SDK (stable), the Android SDK and JDK 17. Streak targets Android 9 (API 28) and up.
Before opening a pull request, keep both of these green:
flutter analyze # no issues
flutter test # all passingIf you touch completions, streaks, scheduling or the importers, add a test — that's where a quiet regression corrupts someone's real history.
Five traps that have already bitten this codebase, none of which fail loudly.
- Date walks need a floor. Avoidance habits are clean by default, so a
loop walking backwards day by day must stop at the habit's
createdAt. Unbounded, it freezes the app. - Never dispose a
TextEditingControllerright afterawait showDialog. The future completes when the dialog starts closing, while the field is still mounted and rebuilding. Own it in aStateinstead. - Never let R8 rename the notification plugin.
flutter_local_notificationsstores its scheduled alarms as JSON written by Gson, which uses the field names. Release builds obfuscate, so a build that renames those fields cannot read what the previous one wrote: the whole store fails withExpected BEGIN_ARRAY but was BOOLEAN, the boot receiver restores nothing and no reminder ever fires again.android/app/proguard-rules.prokeepscom.dexterous.**; do not remove it, and check reminders after touching anything underandroid/app/build.gradle.kts. - Schedule reminders before syncing the widgets. Saving a habit used to sync the home-screen widgets first and reschedule last; closing the app right after saving killed the reminder. Anything that must survive the user leaving goes first, and the form awaits the save before popping.
- Glance draws only the first 10 children of a
RoworColumn— no error, just half a widget. Chunk anything longer, and runflutter cleanafter Kotlin changes or you'll keep testing the old one.
Flutter + Dart, state in provider, storage in hive_ce, charts from
fl_chart, home widgets in Kotlin with Jetpack Glance.
Features are self-contained — each owns its models, controllers, pages and
widgets. Anything that changes a habit's completions belongs in CompletionOps,
which both the UI and the widgets call.
Directory layout
lib/
├── main.dart App entry point and home-widget callback
├── app/ App shell, navigation host, theming
├── core/ Storage, date helpers, icons, routing, shared widgets
├── l10n/ Translation files (source of truth)
├── features/ habits · statistics · focus · settings · onboarding
└── services/ Notifications, widgets, backup, import, launcher icon
android/ Android host project and the Glance widgets
fastlane/ Store listing and changelogs — F-Droid reads these
screenshots/ Screenshots used by the READMEs and app stores
test/ Unit tests
Style: match the code around you. Comments are rare on purpose — prefer a
name that explains itself. Every user-visible string goes in
lib/l10n/app_en.arb and is used via context.l10n.your_key; leave the other
languages to Weblate, and never
rename an existing key or its translations are lost everywhere.
Note
Versions, tags, signing and publishing are handled by the maintainer — never bump a version or push a tag in a pull request. CI builds every PR as a check.
By contributing you agree that your work ships under the project's GNU GPLv3.