Metron’t - T&W Metro App Alternative
Started at the end of May 2025

What is it?⌗
- Platform: Android
- UI Toolkit: Jetpack Compose
- Language: Kotlin
A lot of people seem to think that the official Pop app for the Tyne and Wear Metro system isn’t the best.
I am one of those people. So, combining my special interest in trains with my programming skills, I decide to learn something entirely different to what I normally do and make my own as practice!
I started just last week at the time of writing, and in that time I have had to learn:
- The Kotlin programming language
- Android development practices and architectures
- The Jetpack Compose library
- How REST APIs work and how to use Nexus’s API
- Material Design Guidelines
Do note that it is far from done, I started just last week at the time of writing, and that some icon assets are taken directly from Nexus currently. The latter is an issue that I would definitely sort out before releasing this as a download, if I ever get to that point.
Current capabilities:⌗
- Displays a map of the Tyne and Wear Metro system, drawing each line on an OSM map.
- Shows live locations of trains currently running, updated from Nexus’s API every ten seconds.
- Shows stations along the lines.
Tapping a station reveals a schedule window showing infomation for approaching trains:
- Destination
- Line colour
- Train colour
- Platform
- Due time
- An aproximate description of location
This is not an embedded webview of the official Nexus metro map. It is a reimplementation using native Android libraries like osmdroid and osmbonuspack. Using this library with Compose was hard to wrap my head around at first, as it is not built to be directly compatible with it. However, Compose does allow you to make Android Views, the old UI toolkit, inside of Compose. This helped me massively.
@Composable
fun MetroMapView(
trainStatuses: String,
onShowBottomSheetChange: (Boolean) -> Unit,
onSelectedStationIdChange: (String) -> Unit
) {
val context = LocalContext.current
val mapView = remember { MapView(context) }
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
val linesKmlDocument = KmlDocument()
linesKmlDocument.parseKMLStream(context.assets.open("metromap.kml"), null)
val linesKmlOverlay = linesKmlDocument.mKmlRoot.buildOverlay(mapView, null, null, linesKmlDocument)
val stationsKmlDocument = KmlDocument()
stationsKmlDocument.parseKMLStream(context.assets.open("metrostations.kml"), null)
val stationsKmlOverlay = stationsKmlDocument.mKmlRoot.buildOverlay(mapView, null, StationsKmlStyler(context), stationsKmlDocument)
(stationsKmlOverlay as FolderOverlay).items.forEachIndexed { index, marker ->
(marker as Marker).setOnMarkerClickListener { marker, mapView ->
onShowBottomSheetChange(true)
onSelectedStationIdChange(stationsKmlDocument.mKmlRoot.mItems[index].mId)
true
}
}
The other options on the navigation bar are currently placeholder and do not function. But I’ll get to that. Hopefully.
Credits⌗
- Nexus for their REST API service and icon assets. (Please let me know if you want me to use other assets!)
- danielgjackson for documenting Nexus’s undocumented API.
- osmdroid, used under the Apache 2.0 licence.
- osmbonuspack, used under the LGPL 3.0 licence.