feat: Complete ViewModel extraction and update documentation#4817
Merged
jamesarich merged 35 commits intomainfrom Mar 16, 2026
Merged
feat: Complete ViewModel extraction and update documentation#4817jamesarich merged 35 commits intomainfrom
jamesarich merged 35 commits intomainfrom
Conversation
…wModel extraction
…eate Android implementations' as complete
…dRadioConfigViewModel' as complete
…tings & Node)' as complete
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`
Contributor
There was a problem hiding this comment.
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
MeshtasticUriplusFileService/LocationServiceabstractions, with Android + JVM implementations. - Replace
Android*ViewModelwrappers with sharedSettingsViewModel,RadioConfigViewModel,DebugViewModel,MetricsViewModel, and sharedcore.uiUIViewModelacross 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
handleScannedUricallsCommonUri.parse(uri.uriString)without handling parse failures. On JVMCommonUri.parseusesjava.net.URI(...), which can throw for malformed strings, so this can crash instead of routing toonInvalid. Consider wrapping parsing inrunCatching/tryand invokingonInvalidwhen 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 |
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
UIViewModelclass fromapp/src/main/kotlin/org/meshtastic/app/model/UIViewModel.ktand updated all imports and usages to referencecore.ui.viewmodel.UIViewModelinstead. [1] [2] [3] [4]AndroidRadioConfigViewModel,AndroidSettingsViewModel, andAndroidDebugViewModelwith 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:
ChannelsNavigation,ConnectionsNavigation,NodesNavigation,SettingsNavigation) to use the new ViewModel classes, including changing references fromAndroidMetricsViewModeltoMetricsViewModel. [1] [2] [3] [4]Small code and baseline cleanups:
app/detekt-baseline.xml) to remove issues related to the deleted Android-specific ViewModels and methods.MainActivity.ktto 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.