Skip to content

feat: Complete ViewModel extraction and update documentation#4817

Merged
jamesarich merged 35 commits intomainfrom
feat/kmp-extract-vm
Mar 16, 2026
Merged

feat: Complete ViewModel extraction and update documentation#4817
jamesarich merged 35 commits intomainfrom
feat/kmp-extract-vm

Conversation

@jamesarich
Copy link
Copy Markdown
Collaborator

This pull request removes several Android-specific ViewModel classes and updates the codebase to use their shared (multiplatform) or feature-specific replacements. The changes simplify the code structure, reduce duplication, and improve maintainability by consolidating ViewModel usage throughout the app. Additionally, the detekt baseline is updated to reflect the removal of these classes.

Key changes include:

Removal of Android-specific ViewModels:

  • Deleted the UIViewModel class from app/src/main/kotlin/org/meshtastic/app/model/UIViewModel.kt and updated all imports and usages to reference core.ui.viewmodel.UIViewModel instead. [1] [2] [3] [4]
  • Replaced usages of AndroidRadioConfigViewModel, AndroidSettingsViewModel, and AndroidDebugViewModel with their multiplatform or feature-specific counterparts (RadioConfigViewModel, SettingsViewModel, DebugViewModel) in navigation and screen setup code. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10]

Navigation and ViewModel usage updates:

  • Updated navigation graphs (ChannelsNavigation, ConnectionsNavigation, NodesNavigation, SettingsNavigation) to use the new ViewModel classes, including changing references from AndroidMetricsViewModel to MetricsViewModel. [1] [2] [3] [4]
  • Adjusted helper functions and composables to use the new ViewModel types, ensuring correct dependency injection and state management. [1] [2] [3] [4] [5]

Small code and baseline cleanups:

  • Updated the detekt baseline (app/detekt-baseline.xml) to remove issues related to the deleted Android-specific ViewModels and methods.
  • Minor refactorings in MainActivity.kt to use shared utility functions and new ViewModel imports. [1] [2] [3]

These changes streamline the codebase by reducing platform-specific duplication and leveraging shared logic across platforms.

Delete the tracking files and metadata for the `extract_viewmodels_20260316` track following the successful migration of ViewModels to Kotlin Multiplatform modules.

- Deleted `conductor/tracks/extract_viewmodels_20260316/index.md`
- Deleted `conductor/tracks/extract_viewmodels_20260316/metadata.json`
- Deleted `conductor/tracks/extract_viewmodels_20260316/plan.md`
- Deleted `conductor/tracks/extract_viewmodels_20260316/spec.md`
@jamesarich jamesarich requested a review from Copilot March 16, 2026 18:53
@github-actions github-actions bot added the enhancement New feature or request label Mar 16, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR finishes migrating the remaining app-only / Android-specific ViewModels into shared KMP modules by introducing multiplatform URI + service abstractions and updating Android navigation/UI to use the shared ViewModels. It also updates documentation and detekt baselines to reflect the extraction.

Changes:

  • Introduce MeshtasticUri plus FileService/LocationService abstractions, with Android + JVM implementations.
  • Replace Android*ViewModel wrappers with shared SettingsViewModel, RadioConfigViewModel, DebugViewModel, MetricsViewModel, and shared core.ui UIViewModel across navigation and screens.
  • Update docs, detekt baselines, and add/adjust commonTest/platform tests for the extracted code.

Reviewed changes

Copilot reviewed 54 out of 55 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModelTest.kt Updates constructor wiring for new injected services.
feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/debugging/DebugViewModelTest.kt Adds commonTest coverage for extracted DebugViewModel.
feature/settings/src/commonTest/kotlin/org/meshtastic/feature/settings/SettingsViewModelTest.kt Updates test wiring for new FileService dependency.
feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt Moves location + file I/O behind injected services in commonMain.
feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/debugging/DebugViewModel.kt Removes Android-specific subclass hooks; implements hex helpers in shared code.
feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt Moves CSV export behind FileService using MeshtasticUri.
feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigItemList.kt Converts Android Uri to MeshtasticUri when exporting.
feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt Converts Android Uri to MeshtasticUri for import/export and CSV export.
feature/settings/detekt-baseline.xml Baseline updates due to extraction/refactors.
feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/metrics/MetricsViewModelTest.kt Adds minimal commonTest for extracted MetricsViewModel.
feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/MetricsViewModel.kt Implements CSV export + base64 decoding in shared code via FileService/Okio.
feature/node/src/androidMain/kotlin/org/meshtastic/feature/node/metrics/PositionLog.kt Converts Android Uri to MeshtasticUri for CSV export.
feature/node/detekt-baseline.xml Baseline updates for new shared CSV export logic.
feature/messaging/src/androidMain/kotlin/org/meshtastic/feature/messaging/ui/contact/Contacts.kt Switches scanned-URI handling to MeshtasticUri.
feature/messaging/src/androidMain/kotlin/org/meshtastic/feature/messaging/ui/contact/AdaptiveContactsScreen.kt Switches scanned-URI handling to MeshtasticUri.
docs/roadmap.md Updates roadmap status to reflect completed extraction work.
docs/kmp-status.md Updates KMP migration status after extraction completion.
core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/UIViewModel.kt Moves app-level UIViewModel to shared module and adds URI handling via MeshtasticUri.
core/testing/README.md Documents MockK portability limitations for future iOS readiness.
core/service/src/jvmTest/kotlin/org/meshtastic/core/service/JvmLocationServiceTest.kt Adds JVM test for location service stub.
core/service/src/jvmTest/kotlin/org/meshtastic/core/service/JvmFileServiceTest.kt Adds JVM test for file service behavior on invalid paths.
core/service/src/jvmMain/kotlin/org/meshtastic/core/service/JvmLocationService.kt Adds JVM/Desktop LocationService implementation (stub).
core/service/src/jvmMain/kotlin/org/meshtastic/core/service/JvmFileService.kt Adds JVM/Desktop FileService implementation via local file paths + Okio.
core/service/src/androidUnitTest/kotlin/org/meshtastic/core/service/AndroidLocationServiceTest.kt Adds Android unit test for service initialization.
core/service/src/androidUnitTest/kotlin/org/meshtastic/core/service/AndroidFileServiceTest.kt Adds Android unit test for service initialization.
core/service/src/androidMain/kotlin/org/meshtastic/core/service/AndroidLocationService.kt Adds Android LocationService implementation w/ permission check.
core/service/src/androidMain/kotlin/org/meshtastic/core/service/AndroidFileService.kt Adds Android FileService implementation backed by ContentResolver.
core/service/detekt-baseline.xml Baseline entries for generic exception catches in new services.
core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/LocationService.kt Introduces shared LocationService interface.
core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/FileService.kt Introduces shared FileService interface.
core/common/src/commonTest/kotlin/org/meshtastic/core/common/util/MeshtasticUriTest.kt Adds tests for MeshtasticUri.
core/common/src/commonMain/kotlin/org/meshtastic/core/common/util/MeshtasticUri.kt Introduces multiplatform URI wrapper type.
core/common/src/androidMain/kotlin/org/meshtastic/core/common/util/MeshtasticUriExt.kt Adds Android conversions Uri <-> MeshtasticUri.
conductor/tracks.md Formatting/newline cleanup.
conductor/archive/extract_viewmodels_20260316/spec.md Adds archived track spec for ViewModel extraction.
conductor/archive/extract_viewmodels_20260316/plan.md Adds archived track plan for ViewModel extraction.
conductor/archive/extract_viewmodels_20260316/metadata.json Adds archived track metadata.
conductor/archive/extract_viewmodels_20260316/index.md Adds archived track index.
conductor/archive/deep_dive_docs_20260316/spec.md Adds archived docs deep-dive track spec.
conductor/archive/deep_dive_docs_20260316/plan.md Adds archived docs deep-dive track plan.
conductor/archive/deep_dive_docs_20260316/metadata.json Adds archived track metadata.
conductor/archive/deep_dive_docs_20260316/index.md Adds archived track index.
app/src/main/kotlin/org/meshtastic/app/ui/Main.kt Switches to shared core.ui UIViewModel.
app/src/main/kotlin/org/meshtastic/app/settings/AndroidSettingsViewModel.kt Deletes Android wrapper ViewModel (replaced by shared).
app/src/main/kotlin/org/meshtastic/app/settings/AndroidRadioConfigViewModel.kt Deletes Android wrapper ViewModel (replaced by shared).
app/src/main/kotlin/org/meshtastic/app/settings/AndroidDebugViewModel.kt Deletes Android wrapper ViewModel (replaced by shared).
app/src/main/kotlin/org/meshtastic/app/node/AndroidMetricsViewModel.kt Deletes Android wrapper ViewModel (replaced by shared).
app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt Updates graph to use shared Settings/RadioConfig/Debug ViewModels.
app/src/main/kotlin/org/meshtastic/app/navigation/NodesNavigation.kt Updates graph to use shared MetricsViewModel.
app/src/main/kotlin/org/meshtastic/app/navigation/ContactsNavigation.kt Updates graph to use shared UIViewModel.
app/src/main/kotlin/org/meshtastic/app/navigation/ConnectionsNavigation.kt Updates graph to use shared RadioConfigViewModel.
app/src/main/kotlin/org/meshtastic/app/navigation/ChannelsNavigation.kt Updates graph to use shared RadioConfigViewModel.
app/src/main/kotlin/org/meshtastic/app/model/UIViewModel.kt Deletes Android adapter ViewModel (replaced by shared).
app/src/main/kotlin/org/meshtastic/app/MainActivity.kt Routes deep links through MeshtasticUri to shared UIViewModel.
app/detekt-baseline.xml Removes baseline entries tied to deleted Android ViewModels.
Comments suppressed due to low confidence (1)

core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/UIViewModel.kt:99

  • handleScannedUri calls CommonUri.parse(uri.uriString) without handling parse failures. On JVM CommonUri.parse uses java.net.URI(...), which can throw for malformed strings, so this can crash instead of routing to onInvalid. Consider wrapping parsing in runCatching/try and invoking onInvalid when parsing fails.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +325 to +353
fun savePositionCSV(uri: MeshtasticUri) {
viewModelScope.launch(dispatchers.main) {
val positions = state.value.positionLogs
fileService.write(uri) { sink ->
sink.writeUtf8(
"\"date\",\"time\",\"latitude\",\"longitude\",\"altitude\",\"satsInView\",\"speed\",\"heading\"\n",
)

positions.forEach { position ->
val localDateTime =
Instant.fromEpochSeconds(position.time.toLong())
.toLocalDateTime(TimeZone.currentSystemDefault())
val rxDateTime = "\"${localDateTime.date}\",\"${localDateTime.time}\""

val latitude = (position.latitude_i ?: 0) * 1e-7
val longitude = (position.longitude_i ?: 0) * 1e-7
val altitude = position.altitude
val satsInView = position.sats_in_view
val speed = position.ground_speed
// Kotlin string format is available in common code on 1.9.20+ via String.format,
// but we can just do basic string manipulation if needed.
val heading = "%.2f".format((position.ground_track ?: 0) * 1e-5)

sink.writeUtf8(
"$rxDateTime,\"$latitude\",\"$longitude\",\"$altitude\",\"$satsInView\",\"$speed\",\"$heading\"\n",
)
}
}
}
get() = _currentDeviceProfile.value

open suspend fun getCurrentLocation(): Any? = null
suspend fun getCurrentLocation(): Any? = locationService.getCurrentLocation()
Comment on lines +371 to +376
fun importProfile(uri: MeshtasticUri, onResult: (DeviceProfile) -> Unit) {
viewModelScope.launch {
try {
fileService.read(uri) { source ->
importProfileUseCase(source).onSuccess(onResult).onFailure { throw it }
}
Comment on lines +39 to +41
context.contentResolver.openFileDescriptor(uri.toAndroidUri(), "wt")?.use { pfd ->
FileOutputStream(pfd.fileDescriptor).sink().buffer().use { sink -> block(sink) }
}
Comment on lines +52 to +55
context.contentResolver.openInputStream(uri.toAndroidUri())?.use { inputStream ->
inputStream.source().buffer().use { source -> block(source) }
}
true
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 16, 2026

Codecov Report

❌ Patch coverage is 29.12621% with 73 lines in your changes missing coverage. Please review.
✅ Project coverage is 12.18%. Comparing base (03fbc9a) to head (6531f3d).
⚠️ Report is 5 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../org/meshtastic/core/service/AndroidFileService.kt 0.00% 21 Missing ⚠️
...tic/feature/settings/radio/RadioConfigViewModel.kt 11.76% 15 Missing ⚠️
...in/org/meshtastic/core/ui/viewmodel/UIViewModel.kt 0.00% 11 Missing ⚠️
...tlin/org/meshtastic/core/service/JvmFileService.kt 42.85% 8 Missing ⚠️
.../meshtastic/core/service/AndroidLocationService.kt 0.00% 7 Missing ⚠️
...eshtastic/feature/node/metrics/MetricsViewModel.kt 80.00% 1 Missing and 3 partials ⚠️
...rg/meshtastic/core/common/util/MeshtasticUriExt.kt 0.00% 2 Missing ⚠️
...g/meshtastic/feature/settings/SettingsViewModel.kt 33.33% 2 Missing ⚠️
...astic/feature/settings/debugging/DebugViewModel.kt 0.00% 2 Missing ⚠️
...src/main/kotlin/org/meshtastic/app/MainActivity.kt 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4817      +/-   ##
==========================================
- Coverage   12.22%   12.18%   -0.05%     
==========================================
  Files         530      531       +1     
  Lines       17762    17578     -184     
  Branches     2654     2630      -24     
==========================================
- Hits         2172     2142      -30     
+ Misses      15276    15131     -145     
+ Partials      314      305       -9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Moving these tests to their respective core modules (core:network and core:service) prevents native crash (SIGABRT) in CI caused by Robolectric/MockK when running massive app test suites testing KMP module classes.
This prevents native crash (SIGABRT / 134) in CI when running memory-intensive parallel test suites with Robolectric and MockK.
@jamesarich jamesarich added this pull request to the merge queue Mar 16, 2026
Merged via the queue into main with commit 6e81cee Mar 16, 2026
5 checks passed
@jamesarich jamesarich deleted the feat/kmp-extract-vm branch March 16, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants