Skip to content

Latest commit

 

History

142 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Adhani — أذاني

Adhani Logo
Prayer times, the Adhan, and the Qibla — working even with no connection.

Flutter Dart Kotlin Android

About

Adhani is an Arabic-first Islamic app built with Flutter on top of a deep native Android backend. It gives you accurate location-based prayer times, a live countdown to the next prayer, the call to prayer at the right minute, and a Qibla compass.

The hard part of an app like this isn't the UI — it's staying correct when nobody is looking at it. Adhani puts most of its real logic in native Kotlin so that alarms, the countdown notification and the home-screen widget keep working when the app is killed, the device is rebooted, or there's no connection for a month.

Features

  • Accurate prayer times — fetched from the Aladhan API for your exact coordinates, with 23 calculation methods to choose from (Umm al-Qura, ISNA, Muslim World League, Egypt, Turkey, and more).
  • Genuinely offline — one request caches a full month ahead, so prayer times, alarms, the countdown and the widget all keep working with no network. Reconnecting refreshes automatically.
  • Reliable Adhan alarmsAlarmManager.setAlarmClock survives Doze, and the alarms re-arm themselves every time one fires, so they never run out. Falls back to an inexact alarm if you decline the exact-alarm permission, rather than going silent.
  • Seven Adhan recordings — chosen per prayer, played on the alarm audio stream so notification sounds can't duck them. Preview each one in settings.
  • Live countdown notification — an ongoing notification counting down to the next prayer, and up through the Iqamah window after it. Can be switched off entirely.
  • Home-screen widget — the day's five prayers, a progress ring, and a live chronometer to the next one. Re-derives what's next on every update, so it stays right even when data is stale.
  • Qibla compass — a smooth needle to Mecca from the device's magnetometer, with haptic and audio feedback as you approach alignment, and your great-circle distance to the Kaaba.
  • Hijri date — turns over at Maghrib, when the Islamic day actually begins, not at midnight.
  • Survives reboots and updatesBootReceiver re-arms everything.
  • No accounts, no analytics, no ads — see PRIVACY_POLICY.md.

Architecture

The Dart UI is a relatively thin layer over an independent native Android backend. The two halves do not talk to each other at runtime — they share one SharedPreferences file as the source of truth, so the native side stays fully functional with the Flutter engine dead.

┌───────────────────────────┐        ┌──────────────────────────────┐
│   Flutter / Dart (lib/)   │        │  Native Android (Kotlin)     │
│                           │        │                              │
│  Riverpod providers       │        │  AlarmSchedulerHelper        │
│  4 screens                │        │  PrayerCountdownService (FGS)│
│  Aladhan client           │        │  AdhanAlarmService + Player  │
│  Month cache              │        │  PrayerUpdateWorker          │
│                           │        │  PrayerWidgetProvider        │
└───────────┬───────────────┘        └──────────────┬───────────────┘
            │                                       │
            │        FlutterSharedPreferences       │
            └──────────────►  (shared state)  ◄─────┘
                         prayer_{id}_time
                         prayer_{id}_trigger_millis
                         prayer_month_cache …
                                   ▲
                    MethodChannel('com.myadhan/notification')
                       — used only for commands, not data

Three independent writers of prayer data keep it consistent no matter what is or isn't running: the Dart scheduler, a WorkManager job that fetches on its own (a from-scratch Kotlin HTTP client — not a callback into Dart), and the countdown service's own fast path at the day rollover. All three end by re-arming alarms from the same shared prefs.

See CLAUDE.md for the full contract, including the exact key names that must stay in sync across both languages.

Tech Stack

Flutter

Concern Package
State management flutter_riverpod
Prayer times http → Aladhan API
Location geolocator, geocoding
Qibla flutter_qiblah
Audio audioplayers (compass feedback)
Notifications flutter_local_notifications, timezone
Storage shared_preferences
Connectivity connectivity_plus
Permissions permission_handler
UI flutter_svg, intl, Cairo variable font

Note: prayer times come from the remote Aladhan API on both sides — there is no local astronomical calculation library. The Adhan recordings play through the native player, not audioplayers.

Native Android (Kotlin)

Component Role
AlarmSchedulerHelper The single place AlarmManager is armed — Adhan alarms and silent widget-refresh alarms
PrayerAlarmReceiver Fires at prayer time, starts playback, re-arms the next day
AdhanAlarmService / AdhanPlayer Foreground media playback on the alarm stream, with audio focus
PrayerCountdownService Foreground service driving the live countdown notification
PrayerUpdateWorker Daily WorkManager sync, cache-first so it works offline
PrayerWidgetProvider Home-screen widget, drawn with RemoteViews + generated bitmaps
PrayerMonthCache Kotlin mirror of the Dart month cache
AladhanApiClient Dependency-free HttpURLConnection client
BootReceiver Restores everything after reboot or app update

Project Structure

Adhani/
├── android/app/src/main/
│   ├── kotlin/com/ki1lux/adhani/   # Native backend: services, receivers, workers, widget
│   └── res/                        # Widget & notification layouts, Adhan audio, icons
├── assets/                         # SVG icons, compass graphics, sound effects
├── lib/
│   ├── controller/                 # Thin wrappers over device APIs (location, compass)
│   ├── model/                      # Plain data classes
│   ├── providers/                  # Riverpod state (prayerTimesProvider is the main one)
│   ├── services/                   # Aladhan client, month cache, timezone, config, logging
│   ├── theme/                      # The locked colour palette
│   ├── view/                       # Screens and shared widgets
│   └── prayer_alarm_scheduler.dart # The cross-cutting bridge into the native side
├── test/                           # Cache wire-format, timezone and widget tests
├── CLAUDE.md                       # Architecture & shared-storage contract
├── DESIGN_IDENTITY.md              # Locked visual identity
├── PRIVACY_POLICY.md
└── PLAY_STORE_CHECKLIST.md         # Release checklist

Screenshots

| Home | Prayer Times | Qibla Compass | Settings | |:---:|:---:|:---:| | Simulator Screenshot - iPhone 16e - 2026-08-16 at 18 37 28 | Simulator Screenshot - iPhone 16e - 2026-08-16 at 18 37 33 | Simulator Screenshot - iPhone 16e - 2026-08-16 at 18 37 45

| Simulator Screenshot - iPhone 16e - 2026-08-16 at 18 38 18 |

Getting Started

Prerequisites

  • Flutter SDK 3.7.2+
  • JDK 17
  • Android SDK — minSdk 24, compileSdk/targetSdk 36

Run it

git clone https://github.com/ki1lux/Adhani.git
cd Adhani
flutter pub get
flutter run

No API keys or .env file are needed — the Aladhan and Nominatim APIs are both keyless.

Test background behaviour on a physical Android device. Emulators don't reproduce the Doze and battery-optimisation behaviour this app is specifically built around, and that covers alarms, the countdown notification, boot recovery and the widget.

Everyday commands

flutter analyze                       # static analysis (flutter_lints, clean)
flutter test                          # run the test suite
flutter build apk --release           # release APK
flutter build appbundle --release     # Play Store bundle

cd android && ./gradlew :app:compileDebugKotlin   # verify native changes

Release signing

Release builds are signed from android/key.properties, which is not in version control:

keytool -genkey -v -keystore ~/adhani-upload.jks \
        -keyalg RSA -keysize 2048 -validity 10000 -alias upload

cp android/key.properties.example android/key.properties   # then fill it in

Without that file the release build still succeeds but is signed with the debug key and prints a warning — such a build cannot be uploaded to Google Play. Full pre-launch steps are in PLAY_STORE_CHECKLIST.md.

Privacy

Adhani has no accounts, no analytics and no ads, and the developer runs no server. Your coordinates are sent to two services — Aladhan (to calculate the times) and OpenStreetMap Nominatim (to name your city) — and nothing else leaves the device. Location is never read in the background, and the app works without the permission at all if you pick your city by hand.

Full details: PRIVACY_POLICY.md.

Contributing

Issues and pull requests are welcome. Two things worth knowing first:

  1. Shared preference keys are a cross-language contract. Change one on the Dart side and you must change it on the Kotlin side in the same commit — no compiler will catch a typo across that boundary. See CLAUDE.md.
  2. The visual identity is locked in DESIGN_IDENTITY.md. Read it before any theming change.

Please make sure flutter analyze and flutter test are clean before opening a PR.

Credits


Made with Flutter & Kotlin — by ki1lux

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages