Stundenplan · Noten · Mensa · Abwesenheiten · Nachrichten · To-dos · Erinnerungen
A fully native Kotlin + Jetpack Compose rewrite of POKYH for Android. It combines school data from WebUntis with POKYH services such as the cafeteria, to-dos and reminders in one fast, offline-capable application.
Important
POKYH Android is currently a work in progress. The native app already contains the core product architecture and major school features, but interfaces and behavior can still change while development continues.
- Overview
- Features
- Navigation
- Architecture
- Tech stack
- Requirements
- Getting started
- Configuration & secrets
- Authentication
- Offline mode
- Widgets, notifications & background work
- In-app updates
- Release builds & signing
- Project structure
- Security
- Troubleshooting
- Contributing
- Related projects
- License
POKYH for Android is a native school client built for Landesberufsschule Brixen (LBS Brixen).
The app connects to two systems:
| Service | Used for |
|---|---|
| WebUntis | Timetable, grades and other school-related data |
POKYH Backend (api.pokyh.com) |
Authentication bridge, cafeteria data, to-dos, reminders and POKYH-specific features |
The project is intentionally built as a native Android application instead of continuing the previous Flutter implementation. The production package ID remains compatible with the legacy app so a correctly signed native release can replace an existing Flutter installation rather than appearing as a second app.
The UI is written completely with Jetpack Compose and uses a single-activity architecture, Navigation Compose, Hilt dependency injection, WorkManager, DataStore, Android Keystore and Glance widgets.
A central dashboard provides quick access to the most important school information and actions.
- Configurable in-app dashboard widgets
- Quick access to grades, absences, to-dos and reminders
- Upcoming exam/reminder information
- Messages and profile access
- Offline/service-status feedback
- Account switching without leaving the app
A native timetable experience backed by WebUntis.
- Week and day views
- Horizontal paging between weeks/days
- Detailed lesson information
- Subject coloring and status tags
- Exam, substitution and event indicators
- Holiday and empty-state handling
- Cached timetable data for offline use
- Clear stale/offline indicators
- Jump back to the current day
- Calendar week display
- ICS export for calendar apps
A dedicated grade dashboard with both overview and subject-level details.
- Overall average
- Subject averages
- Expandable subject rows
- Per-subject detail view
- Grade ratio overview
- Grade distribution
- Recently received grades
- Consistent subject colors shared with the timetable
The school hub groups additional student workflows into one place.
- ✅ To-dos
- 🔔 Reminders
- 🚫 Absences
- ✉️ Messages
- 📘 Class register events
- 👥 Classroom / class information
- 🔌 Connection status
The cafeteria experience is backed by the POKYH API.
-
Current dishes
-
Dish images
-
Categories and prices
-
Descriptions
-
Star ratings
-
Personal ratings
-
Comments
-
Allergen information
-
Estimated nutrition values per 100 g
- Calories
- Protein
- Carbohydrates
- Fat
-
Cached data where available
- Multi-account support
- Fast account switching
- WebUntis-linked accounts
- POKYH-only fallback login
- Capability-aware navigation
- Automatic restoration of saved accounts
- Biometric app lock
- Automatic lock when configured and the app returns from the background
Accounts without a valid WebUntis session can still use supported POKYH functionality. In that case WebUntis-only tabs such as Stundenplan and Noten are hidden automatically.
The app is designed to remain useful when WebUntis, the POKYH backend or the device connection is temporarily unavailable.
- Persistent disk cache
- Cached-session fallback
- Automatic offline restoration after network/login timeouts
- Background reconnect attempts
- Exponential reconnect backoff
- Silent upgrade from cached session to live session after reconnect
- Stale-data indicators in the UI
- Queued POKYH operations for supported to-do/reminder actions
- Biometric authentication
- Runtime notification permission handling
- Local reminder notifications
- Periodic background synchronization
- Two Android home-screen widgets
- Native splash screen
- In-app GitHub release updater
- Backend-driven announcement popups
- Material 3 UI
- Edge-to-edge layout
POKYH uses one NavHost with five main tab roots:
Home
├── Profile
└── Connection status
Stundenplan
└── Lesson details
Schule
├── To-dos
├── Reminders
│ └── Reminder details
├── Absences
├── Messages
│ └── Message details
├── Class register events
└── Classroom
Noten
└── Subject details
Mensa
└── Dish details
The app uses separate motion patterns for navigation:
- Tab switches: crossfade
- Push/pop navigation: slide + fade
Stundenplan and Noten are shown only when the active account has WebUntis capabilities.
POKYH combines a central application state with screen-level Hilt ViewModels and dedicated network/storage components.
flowchart TD
UI["Jetpack Compose UI"] --> NAV["Navigation Compose"]
NAV --> VM["Hilt ViewModels"]
UI --> STATE["AppState<br/>application-wide state"]
VM --> STATE
VM --> UNTIS["UntisClient"]
VM --> BACKEND["BackendClient"]
STATE --> UNTIS
STATE --> BACKEND
UNTIS --> WEBUNTIS["WebUntis<br/>LBS Brixen"]
BACKEND --> API["POKYH Backend<br/>api.pokyh.com"]
STATE --> CACHE["DiskCache<br/>offline data"]
STATE --> PREFS["PreferencesStore<br/>DataStore"]
STATE --> CREDS["SecureCredentialStore<br/>Android Keystore / AES-GCM"]
STATE --> HEALTH["NetworkMonitor<br/>ServiceHealth"]
STATE --> WIDGETS["WidgetDataBridge<br/>Glance widgets"]
STATE --> NOTIFY["Notifications"]
STATE --> WORK["WorkManager<br/>background sync"]
Single activity
MainActivity hosts the Compose application and uses FragmentActivity so AndroidX BiometricPrompt can be used directly.
Central AppState
Authentication state, the active account, account switching, offline restoration and auto-lock behavior are coordinated by one application-scoped Hilt singleton.
Screen-level ViewModels Feature screens keep their own UI state while receiving shared application state and services through Hilt.
Separate remote services
UntisClient communicates with the school's WebUntis instance while BackendClient handles POKYH-specific backend calls.
Persistent local state Preferences, encrypted credentials and offline cache data are intentionally separated instead of being stored in one shared persistence layer.
| Area | Technology |
|---|---|
| Language | Kotlin 2.4.20 |
| Android Gradle Plugin | AGP 9.1.0 |
| Build system | Gradle 9.7.1 |
| UI | Jetpack Compose + Material 3 |
| Compose BOM | 2026.06.01 |
| Navigation | Navigation Compose 2.10.0 |
| Dependency injection | Hilt 2.60.1 |
| Code generation | KSP 2.3.12 |
| Networking | OkHttp 5.5.0 |
| Serialization | kotlinx.serialization 1.11.0 |
| Date & time | kotlinx-datetime 0.8.0 |
| Images | Coil 3.6.2 |
| Preferences | DataStore 1.2.1 |
| Background work | WorkManager 2.11.2 |
| App widgets | Glance 1.2.0 |
| Biometrics | AndroidX Biometric |
| Java toolchain | Java 17 |
| Minimum Android | API 26 / Android 8.0 |
| Compile / target SDK | API 37 |
Before building the project, make sure you have:
- Android Studio with support for the current Android Gradle Plugin
- JDK 17
- Android SDK API 37
- A device/emulator running Android 8.0 (API 26) or newer
- Access to the required POKYH backend credentials
- Internet access for live WebUntis/POKYH data
You do not need a separately installed Gradle version; use the included Gradle Wrapper.
git clone https://github.com/bedchem/POKYH_ANDROID.git
cd POKYH_ANDROIDcp local.properties.example local.propertiesThen edit local.properties:
sdk.dir=/path/to/Android/Sdk
POKYH_API_KEY=your_api_key
POKYH_SERVER_KEY=your_server_keyThe API and server keys must match the POKYH backend configuration.
Caution
Never commit local.properties, keystores or real credentials. The file is intended to remain local and is gitignored.
macOS / Linux:
./gradlew :app:assembleDebugWindows:
.\gradlew.bat :app:assembleDebugThe debug APK is generated below:
app/build/outputs/apk/debug/
./gradlew :app:installDebugYou can also open the repository directly in Android Studio and run the app configuration.
The project reads local values from local.properties first and then falls back to environment variables.
| Key | Purpose |
|---|---|
POKYH_API_KEY |
Client API key expected by the POKYH backend |
POKYH_SERVER_KEY |
Server/trusted integration key expected by the backend |
| Key | Purpose |
|---|---|
POKYH_KEYSTORE_PATH |
Path to release keystore |
POKYH_KEYSTORE_PASSWORD |
Keystore password |
POKYH_KEY_ALIAS |
Signing key alias |
POKYH_KEY_PASSWORD |
Signing key password |
Example:
POKYH_KEYSTORE_PATH=/secure/path/pokyh.jks
POKYH_KEYSTORE_PASSWORD=...
POKYH_KEY_ALIAS=...
POKYH_KEY_PASSWORD=...The Android build currently targets:
POKYH backend: https://api.pokyh.com
WebUntis: https://lbs-brixen.webuntis.com/WebUntis
School: lbs-brixen
Update repo: bedchem/POKYH_ANDROID
Note
Values injected into an Android APK at build time can ultimately be extracted from a distributed client. Do not treat client-side values as a replacement for proper server-side authorization and access control.
The authentication flow supports both WebUntis-linked and POKYH-only accounts.
flowchart TD
START["Username + password"] --> UNTIS["Try WebUntis login"]
UNTIS -->|Success| LINK["Authenticate with POKYH backend<br/>using WebUntis-linked flow"]
UNTIS -->|Unavailable / unsupported| DIRECT["Try direct POKYH backend login"]
LINK --> SESSION["Authenticated session"]
DIRECT -->|Success| SESSION
DIRECT -->|Failure| ERROR["Login error"]
SESSION --> CAPS{"WebUntis available?"}
CAPS -->|Yes| FULL["Full navigation"]
CAPS -->|No| POKYH["POKYH-only navigation"]
Saved credentials are not stored in regular DataStore preferences. The project separates them into SecureCredentialStore, which uses Android Keystore-backed AES-GCM encryption.
Offline behavior is coordinated centrally by AppState.
When a saved account exists, the app can race a live login attempt against cached data. If the network is unavailable or authentication services respond too slowly, POKYH can enter a cached offline session instead of blocking the user indefinitely.
The app then continues reconnecting in the background. Once a valid live session becomes available, it can replace the cached session without requiring the user to log in again.
- Cached school/app data is persisted in
DiskCache - Saved account metadata and preferences live in DataStore
- Login credentials are encrypted separately
- Offline session snapshots remove live bearer/API/session tokens before persistence
- Network state and backend/WebUntis health are surfaced to the UI
- Screens mark stale/cached data where relevant
This makes the offline cache a fallback for usability without treating old data as live data.
The app currently ships with two Glance widgets:
| Widget | Purpose |
|---|---|
| POKYH – Nächste Stunde | Shows the next lesson |
| POKYH – Noten | Shows grade information |
The application supports reminder notifications and requests POST_NOTIFICATIONS only after a successful login on Android versions that require runtime permission.
Relevant Android capabilities include:
POST_NOTIFICATIONS
SCHEDULE_EXACT_ALARM
RECEIVE_BOOT_COMPLETED
POKYH initializes WorkManager with a Hilt-aware worker factory rather than using WorkManager's default initializer.
Background responsibilities include periodic notification/data synchronization and keeping relevant app/widget state fresh.
POKYH contains a native updater backed by GitHub Releases.
The updater checks:
https://api.github.com/repos/bedchem/POKYH_ANDROID/releases/latest
A release is considered installable only when:
- It is newer than the currently installed app version
- It is not a draft
- It is not a prerelease
- It contains an
.apkasset
The APK is downloaded into the app cache and validated before Android's package installer is opened.
The updater verifies that:
- The APK can be parsed
- The package name matches the currently installed app
- The APK has a higher
versionCode
Android still asks the user to approve the install, and Android's package manager enforces signing compatibility when replacing an existing installation.
The expected release workflow is:
1. Bump appVersionName in app/build.gradle.kts
2. Build the release APK
3. Create a Git tag/release such as v2.0.1
4. Attach the .apk to the GitHub Release
5. Publish the release (not draft/prerelease)
The in-app updater compares the GitHub tag with BuildConfig.VERSION_NAME.
The current application version is:
2.0.0
versionCode is derived automatically from semantic version components:
2.0.1 -> 20001
2.1.0 -> 20100
3.0.0 -> 30000
This avoids accidentally forgetting to bump versionCode.
The production application ID is intentionally:
dev.plattnericus.project
while the Kotlin namespace is:
dev.plattnericus.pokyh
The production ID is preserved from the former Flutter application so the native Android app can install as an update over the old app.
The debug variant receives the normal debug application ID suffix and can therefore coexist separately during development.
The release build preserves compatibility with the signing identity used by published POKYH APKs. The build supports overriding the signing configuration through the POKYH_KEYSTORE_* values shown above.
Warning
If an APK is signed with a different key, Android will not allow it to update an already installed production POKYH app with the same package ID. Keep the official signing key backed up and do not rotate it accidentally.
Build a release with:
./gradlew :app:assembleReleaseRelease APKs are written below:
app/build/outputs/apk/release/
Release builds enable code shrinking, resource shrinking and ProGuard/R8 processing.
A simplified view of the repository:
POKYH_ANDROID/
├── app/
│ ├── build.gradle.kts
│ └── src/main/
│ ├── AndroidManifest.xml
│ ├── java/dev/plattnericus/pokyh/
│ │ ├── core/
│ │ │ ├── notifications/
│ │ │ ├── update/
│ │ │ └── widgets/
│ │ ├── data/
│ │ ├── state/
│ │ │ └── AppState.kt
│ │ ├── ui/
│ │ │ ├── absences/
│ │ │ ├── classreg/
│ │ │ ├── classroom/
│