An Android home-screen widget experience for live public transport departures, built with Expo and React Native. This project turns real-time departure data into a glanceable, home-screen-first interface for commuters in Stockholm, with provider-aware board styling and a compact configuration app.
Tavlan lets a user choose a transit provider, station, and transport type, then displays current departures in a widget-friendly layout. It currently includes two custom Android widget variants:
- SL "Classic" board
- SL "Modern" board
The widget content is refreshed to match the userβs selected station and is designed to fit the constraints of a home-screen widget while preserving an attractive, information-dense layout.
This project was built to explore the intersection of:
- React Native + Expo for a lightweight native app shell
- Android widgets for home-screen utilities
- live transport data integration
- provider abstraction and pluggable board rendering
- mobile UX design for glanceable information
- Live departures for Stockholm public transport via a provider-based service layer
- Two branded Android widget variants with distinct visual identities
- Station and transport selection inside the app
- Persistent preferences for provider, transport, and stop selection
- Provider isolation with a clean abstraction for future transport operators
- Responsive widget sizing with auto-fit departure row counts
- Tapping a widget triggers a refresh flow for updated departures
- Type-safe data flow between service, app state, and widget rendering layers
For the SL provider, Tavlan supports two branded widget styles: a classic board and a modern board. Both are resizeable and tuned for the commuter experience, with a station header that reflects the current line type such as T-Bana or PendeltΓ₯g.
This version recreates the retro public-transit board aesthetic with a pixel-inspired display, strong station branding, and a compact departure list.
This variant uses a cleaner commuter-panel layout with stronger separation between line badges, destination names, and countdowns while keeping the same home-screen usability.
Header remains recognisable for transport type:
The board adapts to different widget heights and widths without losing legibility, which makes it practical on Android home screens and gives the design a native utility-app feel. The same widget can be compact or tall depending on the userβs home-screen layout, while keeping the SL identity intact.
-
Classic and modern variants both resize vertically
-
Departure rows adjust to the available space so the list remains readable
-
Compact layouts keep the most important information visible first
-
Tapping the widget can trigger a refresh flow for updated departures
These screenshots show the same SL board family at different heights and densities, demonstrating how the layouts remain usable as the widget grows or shrinks.
![]() ![]()
|
![]() ![]()
|
The practical result is a widget system that feels native to Android: compact enough for quick glanceability, but still rich enough to surface multiple departures and destination names when more space is available.
![]() Transport Provider / Type / Station configurator |
![]() Station selection |
![]() Widget preview |
- Expo SDK 56
- React Native 0.85
- Expo Router
- TypeScript
- Android widget support via react-native-android-widget
- SQLite for local persistence
- Custom native-like widget rendering and provider-driven theming
.
βββ app.json # Expo + Android widget configuration
βββ src/
β βββ app/ # App screens and routing
β βββ components/ # Reusable UI primitives
β βββ services/ # Transport provider and data access layer
β βββ utils/ # Local storage helpers
β βββ widgets/ # Widget definitions and shared rendering logic
βββ assets/ # Icons, fonts, widget previews, etc.
βββ android/ # Android project files
βββ plugins/ # Expo config plugins
βββ package.json
βββ README.md
βββ tsconfig.json
- The user selects a transport provider, stop, and travel mode in the app.
- The app stores the selection locally so it survives future sessions.
- The app fetches live departures from the active provider.
- The widget is updated with the selected station and departure set.
- Each widget variant renders board data according to its own design and sizing rules.
This separation keeps the widget layer focused on display while service logic handles provider-specific data and normalization.
Tavlan is based on product-oriented mobile engineering:
- Is built around a real user scenario: checking departures in seconds
- Balances app and widget UX instead of treating them as separate concerns
- Includes abstractions aiming to make adding new providers or board styles straightforward
- Leverages technical design to implement a polished user experience
npm installnpx expo startnpm run androidnpm run webnpm run start
npm run android
npm run web
npm run lint








