Ddr.dev
← Work
Innov8 HRM screenshot

Innov8 HRM

AndroidiOSFlutter · BLoC · GoRouter · Firebase

Introduction

Innov8 HRM puts the full employee lifecycle in a phone. It ships to Android and iOS from one Flutter codebase and serves multiple client companies simultaneously.

Role: Mobile Developer. Stack: Flutter, Dart, BLoC/Cubit, GoRouter, GetIt, Firebase Cloud Messaging, REST, secure storage.

What it does

  • Punch in and out with location-verified attendance, and log break time against a live timer.
  • Apply for leave against live balances, and browse payslips and payroll history.
  • View the company holiday calendar, announcements, assigned tasks, and training enrolment.
  • Reporting managers get an additional approval layer for their team's pending leave and requests.
  • Firebase push notifications deep-link straight to the relevant screen.
Attendance

Attendance opens on a monthly summary — days present, days absent, and a progress bar for the current cycle — with the full punch history underneath. Each row shows check-in and check-out times, and taps through to the detail view.

Runtime multi-tenancy

The hardest architectural constraint was that each customer organisation runs its own backend. Instead of separate builds or a hardcoded host, the app hits a fixed per-environment discovery endpoint at login, resolves that organisation's real API base URL, persists it to encrypted storage, and rebinds every endpoint getter at runtime — so a single binary serves any tenant, and restores the right one on relaunch.

Payroll

Payroll breaks a cycle into basic salary, earnings, and deductions, with the net figure at the centre of the chart. Overtime, bonuses, and pending amounts sit below it, and past cycles are listed in the history beneath.

Resilient authentication

A centralised HTTP client attaches bearer tokens, detects 401 responses, refreshes the token once, and replays the original request — while queueing concurrent in-flight calls so a burst of parallel requests triggers exactly one refresh rather than a stampede. Unrecoverable failures propagate to a global listener that cleanly returns the user to login.

Trustworthy attendance capture

A naive getCurrentPosition() call can hand back a cached fix from hours earlier — unacceptable when the reading is what proves someone was at work. The location layer instead streams position updates within a time budget, selects the freshest reading that meets an accuracy threshold, and rejects mock-location providers, closing the obvious spoofing route.

Attendance detail

The per-day sheet is where that location work surfaces: check-in and check-out times, total hours worked, late status, and the shift and employee it was recorded against.

Product breadth

Roughly eighteen feature domains are each isolated behind their own BLoC or Cubit, wired through a GetIt service locator with deliberate lifetime choices — most state is per-screen and disposable, while break and holiday state are singletons so an in-progress break survives navigation. Routing is centralised in GoRouter, environments are selected at compile time via --dart-define, and the UI is fully localised across English, Sinhala, and Tamil.

Profile

Profile collects the account-level surfaces — editing personal details, reading company policies, app settings, and sign-out — kept deliberately plain so it reads the same across all three supported languages.