Kreta is a train tracking app for Indonesian railway passengers. It helps people plan a trip, follow the train on a map, and get warned before they miss their stop.
The idea came from a small frustration I kept running into. I take trains often, and it felt strange that flights had apps like Flighty while trains still mostly had schedule apps and static maps. I wanted something closer to a travel companion: where is my train, what station did I just pass, and when should I wake up?
Kreta started as our final challenge project at Apple Developer Academy @ UC Surabaya. I was the tech lead on a five-person team, and we built it as a native SwiftUI app with Convex as the backend. The interesting part was making the app feel like a real travel companion, not just a pretty map with schedule data.
The stack was a little weird in a good way. SwiftUI, MapKit, ActivityKit, AlarmKit, WidgetKit, Convex, APNs, Core Location, and scraping-based train schedule data all had to meet in one product.

What I built
Real-time train tracking on a map
The core feature is simple to explain: show where the train is and where it is going.
The implementation was less simple. Indonesian train data is schedule-heavy, so the app needed to project train position from route and timing data instead of relying on perfect live GPS. I worked on rendering trains, stations, and routes on the map, then making the movement feel understandable enough for passengers who just want to know, "am I close yet?"
We even tried to solve this properly by reaching out to KAI ourselves. We got a few minutes to talk with the tech lead from the IT division behind KAI Access, the official train app. We asked whether we could get real train GPS data so Kreta could be more accurate, but they could not approve it because of national security concerns.
That answer shaped the product. Kreta had to be honest about estimation and make schedule-based projection as useful as possible instead of pretending we had official live GPS.
The app used MapKit because the product depended on native map behavior, route rendering, and location context. That made the map work a core product decision, not just a UI decision.

Journey planning and train selection
Kreta is not only a map. Users need to choose origin, destination, date, and train, then keep that journey active while they travel.
I worked on the flow around adding a train journey, station search, schedule browsing, filtering, and storing the selected trip state. The hard part was making the data model line up with the product flow. A train schedule can look like a list, but the user experiences it as a journey that starts somewhere, moves through stations, and ends with a stop they really do not want to miss.
This is where Convex helped. The backend could keep stations, trains, routes, journeys, feedback, and device registrations in a schema that the clients could subscribe to in real time.

Lock Screen updates and critical alarms
The most product-specific part of Kreta was the alarm experience.
A normal notification is easy to miss. That is a problem when the whole point is waking someone up before their destination. The native iOS app used Live Activities for Lock Screen and Dynamic Island updates, then AlarmKit for stronger arrival alarms that can override silent mode on supported iOS versions.
This was the kind of work where the feature sounds small in a product description, but touches a lot of systems: app state, permissions, background updates, APNs, ActivityKit attributes, WidgetKit UI, and server-side schedules.
AlarmKit also gave us the most annoying bug in the project. During testing, if the alarm fired while the user was actively using the phone, iOS entered the alerting state but stayed silent. No banner, no sound, no vibration. It just left a blank space on the Dynamic Island and blocked other Live Activities.
I rebuilt the logic, tested edge cases, doubted the implementation, then installed the iOS 26.2 beta during development to check whether the bug was ours or Apple's. The fix only appeared in 26.2. That was painful, but it also made the final alarm experience feel much more real once it worked.

Native iOS architecture with Convex
Kreta was built as a native iOS app because the core features depended heavily on Apple platform APIs. Live Activities, AlarmKit, MapKit, Core Location, WidgetKit, and background behavior were not decorations. They shaped the architecture.
Convex sat behind the app as the real-time backend for train data, journey state, feedback, and device-related records. The app needed to feel local and reliable on the phone, but it also needed server state for schedules, user feedback, and trip-related updates.
That split became the main architecture decision: keep the iOS experience deeply native, then use Convex for the data layer that had to stay fresh and connected.
Feedback board and product loop
I also built a public feedback board with categories, upvotes, and real-time updates through Convex.
This mattered because Kreta was not only a technical experiment. It was an app people could actually use, and train apps depend on trust. If something is wrong, unclear, or missing, users need an obvious place to say it.
After launch
Kreta ended up reaching almost 500 monthly active users, with 492 unique users in January 2026. It also reached #66 in the Travel category on the Indonesian App Store.
That was a small but meaningful milestone. The app started from our own train-rider frustration, then turned into something real people were opening before and during their trips.
Kreta also got covered by local media in UrbanIdea ID: Revanza Kurniawan: Bangun Aplikasi Kreta buat Bantu yang Pergi Antar Kota. I treated that as a milestone too. It was the first time the project felt like it had moved beyond our academy bubble and into a more public conversation.
Below are the two proof points I would keep here: the App Store Travel ranking first, then the January 2026 active-user screenshot.


What I learned
Kreta taught me that transportation apps are mostly edge cases pretending to be simple maps.
The UI can look calm, but underneath it there are schedules, station order, route geometry, background execution, permissions, bad network, late trains, and platform-specific rules. A tiny mistake in timing or state can turn into a very real product failure because the user is depending on the app while moving.
The biggest lesson was about native features. Live Activities, alarms, widgets, and map behavior are not decorations. They change the architecture. You have to think about what keeps running when the app is not open, what the server needs to know, what can be pushed, and what the OS will simply refuse to do.
Kreta also made me more careful about platform-specific product work. It is tempting to think of maps, alarms, widgets, and background updates as feature add-ons. In practice, they decide the shape of the app. The product only works if the native behavior works.
Also, maps always take longer than I think. Always.