
Fetches real-time London Underground tube status using Transport for London API. Offers ready-made UI components, authentic branding, and seamless offline handling for Android and iOS applications.
π§© Real-time London Underground status for Android & iOS β built with Kotlin Multiplatform and Compose Multiplatform.
A Kotlin Multiplatform library providing real-time London Underground tube line status information with ready-to-use UI components and shared business logic for Android and iOS applications.
The project consists of three main modules:
Clone the repository:
git clone https://github.com/IntSoftDev/LondonTubeStatus.git
cd LondonTubeStatusSetup TFL API credentials:
Create local.properties file:
tfl.app.id=your_tfl_app_id
tfl.app.key=your_tfl_app_keyConfigure local development:
In gradle.properties, set:
importLocalKmp=trueRun the sample app:
./gradlew :composeApp:assembleDebug# Android-only build
./gradlew assembleDebug
# Tests
./gradlew allTests
# Lint check
./gradlew ktlintCheck
# Full build
./gradlew build
# Clean build cache
./gradlew cleanLondonTubeStatus/
βββ composeApp/ # Sample Android/iOS app
β βββ src/
β βββ androidMain/ # Android-specific code
β βββ commonMain/ # Shared Compose UI
β βββ iosMain/ # iOS-specific code
βββ tflstatus-core/ # Core business logic library
β βββ src/
β βββ commonMain/ # Shared business logic
β βββ androidMain/ # Android HTTP client
β βββ iosMain/ # iOS HTTP client
βββ tflstatus-ui/ # UI components library
β βββ src/
β βββ commonMain/ # Compose Multiplatform UI
β βββ ...
βββ iosApp/ # iOS app wrapper
βββ PUBLISHING_GUIDE.md # Library publishing documentation
To use the pre-built artifacts:
In gradle.properties, set:
importLocalKmp=falseAdd to your build.gradle.kts:
dependencies {
// Option 1: Complete UI solution (recommended)
implementation("com.intsoftdev:tflstatus-core:<version>")
implementation("com.intsoftdev:tflstatus-ui:<version>")
// Option 2: Core library only (custom UI)
implementation("com.intsoftdev:tflstatus-core:<version>")
}The TFL SDK can be initialised in your Application class with various configuration options:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Option 1 - Basic initialisation without using API keys or Koin
initTflSDK(context = this)
// With custom API configuration
val apiConfig = TflApiConfig(
appId = "your-app-id",
appKey = "your-api-key"
)
// Option 2 - if App does not use Koin
initTflSDK(context = this, apiConfig = apiConfig)
// Option 3 - with Koin
val koinApp = startKoin { /* your modules */ }
initTflSDK(context = this, koinApplication = koinApp, apiConfig = apiConfig)
}
}@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") {
HomeScreen(onNavigateToTFL = {
navController.navigate("tfl_status")
})
}
composable("tfl_status") {
tflStatusUI(onBackPressed = {
navController.popBackStack()
})
}
}
}The UI library uses authentic TFL branding but can be customised:
@Composable
fun CustomThemedTFL() {
MaterialTheme(
colorScheme = yourCustomColorScheme,
typography = yourCustomTypography
) {
tflStatusUI(onBackPressed = { /* handle back */ })
}
}Add the TFL Status library to your iOS project using CocoaPods:
Local Development
# Podfile
platform :ios, '17.0'
install! 'cocoapods', :deterministic_uuids => false
target 'YourApp' do
use_frameworks!
# Local paths - adjust to match your project structure
pod 'tflstatus_core', :path => '../tflstatus-core/'
pod 'tflstatus_ui', :path => '../tflstatus-ui/'
endThen run:
cd ios
pod install// From iOS Swift code:
let viewController = TFLStatusBridgeKt.createTFLStatusViewController(
showBackButton: true,
onBackPressed: {
// Handle back button press
},
enableLogging: true,
apiConfig: TflApiConfig()
)Add the framework to your iOS project and use in SwiftUI:
import SwiftUI
import tflstatus_ui
import tflstatus_core
struct TFLStatusComposeView: UIViewControllerRepresentable {
var showBackButton: Bool = true
var onBackPressed: (() -> Void)?
func makeUIViewController(context: Context) -> UIViewController {
return TFLStatusBridgeKt.createTFLStatusViewController(
showBackButton: showBackButton,
onBackPressed: onBackPressed ?? {
},
enableLogging: true,
apiConfig: TflApiConfig(
appId: "your-app-id",
appKey: "your-api-key"
)
)
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
// No-op: Compose content is self-updating
}
}
struct TFLStatusScreen: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
ZStack {
Color.black
.navigationBarTitleDisplayMode(.inline)
.edgesIgnoringSafeArea(.all)
// Pipe SwiftUI's dismiss into KMP's onBackPressed
TFLStatusComposeView(
showBackButton: true,
onBackPressed: { dismiss() }
)
}
.toolbar {
ToolbarItem(placement: .principal) {
Text("TFL Status")
.foregroundColor(.white)
.font(.headline)
}
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
dismiss()
}) {
Image(systemName: "arrow.left")
.foregroundColor(.white)
})
}
.preferredColorScheme(.dark)
}
}
// Usage in your main ContentView
struct ContentView: View {
@State private var showTFLStatus = false
var body: some View {
NavigationView {
VStack {
Button("London Tube Status") {
showTFLStatus = true
}
.buttonStyle(.borderedProminent)
}
.navigationTitle("Your App")
.sheet(isPresented: $showTFLStatus) {
TFLStatusScreen()
}
}
}
}@Composable
fun CustomTFLScreen(viewModel: TubeStatusViewModel = koinInject()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.getLineStatuses("victoria,central,northern")
}
when (val state = uiState) {
is TubeStatusUiState.Loading -> LoadingScreen()
is TubeStatusUiState.Success -> CustomLineList(state.tubeLines)
is TubeStatusUiState.Error -> ErrorScreen(state.message)
}
}| Android | iOS |
|---|---|
| Used in Play Store app | Used in Apple App Store |
Libraries are published to Maven Central:
com.intsoftdev:tflstatus-core
com.intsoftdev:tflstatus-ui
See PUBLISHING_GUIDE.md for detailed publishing instructions.
git checkout -b feature/<feature_name>)git commit -m 'Add <feature>')git push origin feature/<feature_name>)This project is licensed under the MIT License - see the LICENSE file for details.
π§© Real-time London Underground status for Android & iOS β built with Kotlin Multiplatform and Compose Multiplatform.
A Kotlin Multiplatform library providing real-time London Underground tube line status information with ready-to-use UI components and shared business logic for Android and iOS applications.
The project consists of three main modules:
Clone the repository:
git clone https://github.com/IntSoftDev/LondonTubeStatus.git
cd LondonTubeStatusSetup TFL API credentials:
Create local.properties file:
tfl.app.id=your_tfl_app_id
tfl.app.key=your_tfl_app_keyConfigure local development:
In gradle.properties, set:
importLocalKmp=trueRun the sample app:
./gradlew :composeApp:assembleDebug# Android-only build
./gradlew assembleDebug
# Tests
./gradlew allTests
# Lint check
./gradlew ktlintCheck
# Full build
./gradlew build
# Clean build cache
./gradlew cleanLondonTubeStatus/
βββ composeApp/ # Sample Android/iOS app
β βββ src/
β βββ androidMain/ # Android-specific code
β βββ commonMain/ # Shared Compose UI
β βββ iosMain/ # iOS-specific code
βββ tflstatus-core/ # Core business logic library
β βββ src/
β βββ commonMain/ # Shared business logic
β βββ androidMain/ # Android HTTP client
β βββ iosMain/ # iOS HTTP client
βββ tflstatus-ui/ # UI components library
β βββ src/
β βββ commonMain/ # Compose Multiplatform UI
β βββ ...
βββ iosApp/ # iOS app wrapper
βββ PUBLISHING_GUIDE.md # Library publishing documentation
To use the pre-built artifacts:
In gradle.properties, set:
importLocalKmp=falseAdd to your build.gradle.kts:
dependencies {
// Option 1: Complete UI solution (recommended)
implementation("com.intsoftdev:tflstatus-core:<version>")
implementation("com.intsoftdev:tflstatus-ui:<version>")
// Option 2: Core library only (custom UI)
implementation("com.intsoftdev:tflstatus-core:<version>")
}The TFL SDK can be initialised in your Application class with various configuration options:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Option 1 - Basic initialisation without using API keys or Koin
initTflSDK(context = this)
// With custom API configuration
val apiConfig = TflApiConfig(
appId = "your-app-id",
appKey = "your-api-key"
)
// Option 2 - if App does not use Koin
initTflSDK(context = this, apiConfig = apiConfig)
// Option 3 - with Koin
val koinApp = startKoin { /* your modules */ }
initTflSDK(context = this, koinApplication = koinApp, apiConfig = apiConfig)
}
}@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") {
HomeScreen(onNavigateToTFL = {
navController.navigate("tfl_status")
})
}
composable("tfl_status") {
tflStatusUI(onBackPressed = {
navController.popBackStack()
})
}
}
}The UI library uses authentic TFL branding but can be customised:
@Composable
fun CustomThemedTFL() {
MaterialTheme(
colorScheme = yourCustomColorScheme,
typography = yourCustomTypography
) {
tflStatusUI(onBackPressed = { /* handle back */ })
}
}Add the TFL Status library to your iOS project using CocoaPods:
Local Development
# Podfile
platform :ios, '17.0'
install! 'cocoapods', :deterministic_uuids => false
target 'YourApp' do
use_frameworks!
# Local paths - adjust to match your project structure
pod 'tflstatus_core', :path => '../tflstatus-core/'
pod 'tflstatus_ui', :path => '../tflstatus-ui/'
endThen run:
cd ios
pod install// From iOS Swift code:
let viewController = TFLStatusBridgeKt.createTFLStatusViewController(
showBackButton: true,
onBackPressed: {
// Handle back button press
},
enableLogging: true,
apiConfig: TflApiConfig()
)Add the framework to your iOS project and use in SwiftUI:
import SwiftUI
import tflstatus_ui
import tflstatus_core
struct TFLStatusComposeView: UIViewControllerRepresentable {
var showBackButton: Bool = true
var onBackPressed: (() -> Void)?
func makeUIViewController(context: Context) -> UIViewController {
return TFLStatusBridgeKt.createTFLStatusViewController(
showBackButton: showBackButton,
onBackPressed: onBackPressed ?? {
},
enableLogging: true,
apiConfig: TflApiConfig(
appId: "your-app-id",
appKey: "your-api-key"
)
)
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
// No-op: Compose content is self-updating
}
}
struct TFLStatusScreen: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
ZStack {
Color.black
.navigationBarTitleDisplayMode(.inline)
.edgesIgnoringSafeArea(.all)
// Pipe SwiftUI's dismiss into KMP's onBackPressed
TFLStatusComposeView(
showBackButton: true,
onBackPressed: { dismiss() }
)
}
.toolbar {
ToolbarItem(placement: .principal) {
Text("TFL Status")
.foregroundColor(.white)
.font(.headline)
}
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
dismiss()
}) {
Image(systemName: "arrow.left")
.foregroundColor(.white)
})
}
.preferredColorScheme(.dark)
}
}
// Usage in your main ContentView
struct ContentView: View {
@State private var showTFLStatus = false
var body: some View {
NavigationView {
VStack {
Button("London Tube Status") {
showTFLStatus = true
}
.buttonStyle(.borderedProminent)
}
.navigationTitle("Your App")
.sheet(isPresented: $showTFLStatus) {
TFLStatusScreen()
}
}
}
}@Composable
fun CustomTFLScreen(viewModel: TubeStatusViewModel = koinInject()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.getLineStatuses("victoria,central,northern")
}
when (val state = uiState) {
is TubeStatusUiState.Loading -> LoadingScreen()
is TubeStatusUiState.Success -> CustomLineList(state.tubeLines)
is TubeStatusUiState.Error -> ErrorScreen(state.message)
}
}| Android | iOS |
|---|---|
| Used in Play Store app | Used in Apple App Store |
Libraries are published to Maven Central:
com.intsoftdev:tflstatus-core
com.intsoftdev:tflstatus-ui
See PUBLISHING_GUIDE.md for detailed publishing instructions.
git checkout -b feature/<feature_name>)git commit -m 'Add <feature>')git push origin feature/<feature_name>)This project is licensed under the MIT License - see the LICENSE file for details.