The Flutter Kit logoThe Flutter Kit
Guide

Flutter Habit Starter Kit — Build a Habit Tracker App in Flutter (2026 Guide)

A practical build guide for a production-ready Flutter habit tracker — data model, streaks, UI, notifications, and monetization.

Ahmed GaganAhmed Gagan
16 min read

Habit trackers are one of the most reliable indie app categories — small scope, sticky retention, clear subscription upsell. I have helped several developers ship habit apps on Flutter and the same mistakes keep coming up: broken streak math, slow Firestore queries, no offline support, and weak notifications. This guide walks through a production-grade Flutter habit starter kit architecture so you do not repeat them.

We will cover the data model, Firestore schema, streak calculation (with the gotchas), the UI patterns that actually convert (heatmap, grid, check-in), reminder notifications, and the RevenueCat subscription tier. By the end you will have enough to ship your own habit tracker on top of a solid Flutter foundation.

What a Production Habit Tracker App Needs

Strip it down and a habit tracker has five jobs. Nail these five and you have a shippable product.

  1. Create and edit habits — frequency, target, icon, color
  2. Daily check-in — mark complete in one tap, feel rewarding
  3. Streak tracking — current streak, longest streak, calendar visualization
  4. Reminders — local notifications at user-selected times
  5. Insights — weekly/monthly stats, heatmap, completion rate

Everything else — social sharing, widgets, AI coaching, mood tracking — is a v2 feature. Ship v1 with just these five and iterate based on retention data.

The Data Model

Three entities: Habit, HabitLog, and Streak. Keep Streak derived from logs when possible — storing it leads to sync bugs. Here is the Dart model.

// lib/features/habits/models/habit.dart
class Habit {
  final String id;
  final String title;
  final String emoji;
  final int colorValue;
  final HabitFrequency frequency;
  final int targetPerPeriod;
  final TimeOfDay? reminderTime;
  final DateTime createdAt;
  final bool archived;

  Habit({
    required this.id,
    required this.title,
    required this.emoji,
    required this.colorValue,
    required this.frequency,
    required this.targetPerPeriod,
    this.reminderTime,
    required this.createdAt,
    this.archived = false,
  });
}

enum HabitFrequency { daily, weekly, custom }

class HabitLog {
  final String id;
  final String habitId;
  final DateTime date;       // Normalized to midnight local time
  final int count;           // Support multi-check-in habits (e.g., 3 glasses of water)
  final String? note;

  HabitLog({
    required this.id,
    required this.habitId,
    required this.date,
    this.count = 1,
    this.note,
  });
}

class Streak {
  final int current;
  final int longest;
  final DateTime? lastCompleted;

  const Streak({required this.current, required this.longest, this.lastCompleted});
}

Firestore Schema

User-scoped subcollections are the correct pattern here. Every read and write is tied to an authenticated user and you can enforce it trivially in security rules.

users/{userId}
  ├── profile (document)
  ├── habits/{habitId}         (collection)
  │     ├── title, emoji, colorValue, frequency, ...
  └── logs/{logId}              (collection)
        ├── habitId, date (YYYY-MM-DD), count, note

Why flat logs instead of habits/{habitId}/logs? Because you will query across all habits for a given date (today's dashboard) more often than logs for a specific habit. A flat collection with a habitId field and a composite index on (habitId, date DESC) gets you both query patterns efficiently.

Security rules for this layout:

rules_version = '2';
service cloud.firestore {
  match /databases/{db}/documents {
    match /users/{userId}/{document=**} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}

Streak Calculation (The Gotchas)

This is where most habit apps bug out. The naive "count consecutive days with a log" breaks around timezones, daylight saving time, and weekly-frequency habits. Here is the version I use in production.

// lib/features/habits/logic/streak_calculator.dart
class StreakCalculator {
  /// Calculates current and longest streak from a list of logs.
  /// Logs must be sorted by date DESCENDING.
  Streak calculate(Habit habit, List<HabitLog> logs) {
    if (logs.isEmpty) return const Streak(current: 0, longest: 0);

    final today = _startOfDay(DateTime.now());
    var current = 0;
    var longest = 0;
    var running = 0;
    DateTime? previous;
    DateTime? lastCompleted;

    // Track which dates are covered (in case of multi-log same day)
    final covered = logs
        .map((l) => _startOfDay(l.date))
        .toSet()
        .toList()
      ..sort((a, b) => b.compareTo(a));

    for (final date in covered) {
      lastCompleted ??= date;
      if (previous == null) {
        running = 1;
      } else {
        final expected = previous.subtract(Duration(days: _stepFor(habit)));
        if (date.isAtSameMomentAs(expected)) {
          running += 1;
        } else {
          longest = running > longest ? running : longest;
          running = 1;
        }
      }
      previous = date;
    }
    longest = running > longest ? running : longest;

    // Current streak: only valid if last check-in is today OR (today - step)
    final mostRecent = covered.first;
    final gapFromToday = today.difference(mostRecent).inDays;
    current = (gapFromToday <= _stepFor(habit)) ? running : 0;

    return Streak(current: current, longest: longest, lastCompleted: lastCompleted);
  }

  int _stepFor(Habit habit) => switch (habit.frequency) {
        HabitFrequency.daily => 1,
        HabitFrequency.weekly => 7,
        HabitFrequency.custom => 1,
      };

  DateTime _startOfDay(DateTime d) => DateTime(d.year, d.month, d.day);
}

Three things to note. First, normalize to start-of-day in the user's local time before comparing — otherwise two logs from the same day with different timestamps will count as separate days. Second, deduplicate dates before running the loop — multi-check-in habits otherwise inflate the streak. Third, the current streak is only live if today's slot is still "in range" — a daily habit not checked in today is still "current" until end of day.

UI Patterns That Convert

Three UI patterns drive habit-app retention. Pick one as your primary screen and use the others as complementary views.

Daily Grid (Home Screen)

A grid of habit cards, each with emoji, title, current streak, and a giant tap-target circle for check-in. One tap should complete the habit and trigger a satisfying haptic + animation. If check-in takes more than 500ms end-to-end, you have already lost retention.

Heatmap (Detail Screen)

The GitHub-style contribution grid is the gold standard for habit history. It communicates streak visually in one glance and rewards users for showing up. Use a GridView.builder with 7 rows (days of week) x N columns (weeks) and color each cell by completion count.

Check-in Sheet

For habits with notes, multi-count (water intake), or mood tracking, surface a bottom sheet on tap instead of instant completion. Let users quickly add context without a full-screen form.

Local Notifications for Reminders

Use flutter_local_notifications for reminders. Schedule a recurring notification at the user's chosen time and cancel it if they disable the habit or change the time. Handle the iOS permission prompt thoughtfully — ask after they create their first habit, not on first launch.

// lib/services/notification_service.dart
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/timezone.dart' as tz;

class NotificationService {
  final _plugin = FlutterLocalNotificationsPlugin();

  Future<void> scheduleHabitReminder(Habit habit) async {
    if (habit.reminderTime == null) return;
    final now = tz.TZDateTime.now(tz.local);
    var scheduled = tz.TZDateTime(
      tz.local,
      now.year, now.month, now.day,
      habit.reminderTime!.hour, habit.reminderTime!.minute,
    );
    if (scheduled.isBefore(now)) {
      scheduled = scheduled.add(const Duration(days: 1));
    }
    await _plugin.zonedSchedule(
      habit.id.hashCode,
      habit.title,
      'Time to check in — keep your streak alive',
      scheduled,
      const NotificationDetails(
        android: AndroidNotificationDetails('habits', 'Habit Reminders'),
        iOS: DarwinNotificationDetails(),
      ),
      matchDateTimeComponents: DateTimeComponents.time,
      androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
    );
  }

  Future<void> cancelHabitReminder(String habitId) {
    return _plugin.cancel(habitId.hashCode);
  }
}

Monetization: The Pro Tier

Habit apps monetize best with a clear free-vs-pro split. Keep the core loop free (3-5 habits, basic streaks, reminders) and gate the features that power users want. Here is the split that converts at 4-6% in my experience.

FeatureFreePro
Active habits3Unlimited
Streak trackingYesYes
Reminders1 per habitUnlimited (multi-time)
Heatmap history30 daysFull history
Custom colors + iconsPresetFull library
Widgets (iOS/Android)NoYes
Data export (CSV)NoYes
iCloud / Google Drive syncNoYes
Ad-freeNoYes

Wire this up with RevenueCat — gate each feature with an entitlement check, and show the paywall at natural moments (adding a 4th habit, opening widgets, viewing extended history). See Flutter paywall design best practices for the full playbook.

Analytics That Matter

Habit apps live or die on retention. Track these events religiously from day one (PostHog or Firebase Analytics both work):

  • habit_created — first habit created (activation signal)
  • habit_checked_in — daily active behavior
  • streak_reached — 3, 7, 14, 30, 60, 100 day milestones
  • streak_broken — churn indicator
  • paywall_viewed — with a source property (add_habit, widgets, history)
  • subscription_started — conversion
  • notification_opened — reminder effectiveness

With these events you can answer: "What percentage of users who hit a 7-day streak convert to pro?" — the single most actionable metric for a habit app.

Offline Support

Users will check in on the subway. Firestore's offline persistence handles most of this automatically on mobile, but you should verify: run your app in airplane mode, check in a habit, close the app, reopen, and confirm the log persists. Also test the sync when connectivity returns — Firestore resolves conflicts last-write-wins by default, which is fine for habit logs.

Shipping on Top of The Flutter Kit

You do not need to build all the infrastructure from scratch. The Flutter Kit gives you a production-ready Flutter base — auth, onboarding, RevenueCat paywalls, Firebase backend, Material 3 design system, BLoC architecture, analytics hooks. You extend it with the habit-specific models, screens, and streak logic described above.

In practice: clone The Flutter Kit, run the setup CLI, add a features/habits/ directory with the models + BLoC + screens from this guide, wire reminders via the notification service, and gate pro features behind the existing RevenueCat entitlement. You skip 100+ hours of boilerplate and focus entirely on what makes your habit app different.

Grab The Flutter Kit on the checkout page — $69 one-time, unlimited commercial apps. Or browse the features to see what ships out of the box.

Ship It Checklist

  • Habit model with emoji, color, frequency, target
  • Flat Firestore logs collection + composite index
  • Streak calculator with timezone-safe date normalization
  • Daily grid home screen with one-tap check-in
  • Heatmap detail screen with full history
  • Local notifications for reminders with permission UX
  • RevenueCat free/pro split with gated features
  • Retention analytics: created, checked_in, streak_reached, paywall_viewed
  • Offline tested in airplane mode
  • Delete-account flow (Apple requires it)

Tick every box and you have a real product, not a prototype. Now go build something people will open every morning.

Share this article

Ready to ship your Flutter app faster?

The Flutter Kit gives you a production-ready Flutter codebase with onboarding, paywalls, auth, AI integrations, and more. Stop building boilerplate. Start building your product.

Get The Flutter Kit