iOS with Swift, Android with Kotlin
SwiftUI state management, Jetpack Compose recomposition and the differences between iOS and Android lifecycles demand platform specific knowledge that generic code often ignores. Claude supports native development for both platforms, explains store review requirements, and helps make architecture decisions for shared business logic on solid grounds.
Table of contents
- 1. Why mobile app development benefits from Claude
- 2. Designing clean SwiftUI state management with Claude
- 3. Understanding Jetpack Compose and recomposition
- 4. Handling platform specific lifecycles correctly
- 5. Shared business logic: Kotlin Multiplatform as an option
- 6. Permissions, privacy and store policies
- 7. Finding performance problems in lists and animations
- 8. Generating UI tests for SwiftUI and Compose
- 9. iOS and Android development in direct comparison
- 10. Summary
- 11. FAQ
1. Why mobile app development benefits from Claude
Mobile development for iOS and Android requires deep knowledge of two different platforms with their own UI frameworks, lifecycles and store policies, which makes context switching between the two ecosystems costly. Claude knows the peculiarities of SwiftUI and Jetpack Compose equally well and helps apply platform specific patterns correctly, instead of transferring concepts from one platform to the other without reflection, which is a particularly common source of subtle bugs in mobile development.
The practical benefit shows up mainly on recurring tasks: setting up new screens with correct state management, phrasing permission requests according to current store policies, identifying performance problems in lists, and weighing cross platform architecture decisions on solid grounds. The following sections show concretely how Claude supports SwiftUI, Jetpack Compose, lifecycles, shared logic and store compliance.
2. Designing clean SwiftUI state management with Claude
SwiftUI offers several property wrappers for state management whose correct use is not always obvious: @State for local view state, @StateObject for objects created and owned by the view itself, @ObservedObject for objects passed in from outside, and @EnvironmentObject for state shared across the view hierarchy. A common mistake is confusing @StateObject and @ObservedObject, which causes an observable object to be recreated on every re render of the parent view and to lose its state.
Claude reliably recognizes this pattern during code review and explains the difference concretely based on object lifetime: if a view owns the object and is responsible for its lifecycle, @StateObject belongs there. If the object is passed in from a parent view, @ObservedObject is correct. With the newer @Observable macro from the Observation framework, this distinction becomes simpler still, because fewer explicit wrappers are needed, though Claude also points out the remaining pitfalls around object creation here.
// OrderListView.swift - correct ownership with @StateObject vs @ObservedObject
import SwiftUI
// The view creates and owns this view model, so use @StateObject
struct OrderListView: View {
@StateObject private var viewModel = OrderListViewModel()
var body: some View {
List(viewModel.orders) { order in
OrderRowView(order: order)
}
.task {
await viewModel.loadOrders()
}
}
}
// This view receives an already-created view model, so use @ObservedObject
struct OrderRowView: View {
@ObservedObject var order: OrderRowViewModel
var body: some View {
HStack {
Text(order.title)
Spacer()
Text(order.formattedTotal)
}
}
}
3. Understanding Jetpack Compose and recomposition
Jetpack Compose follows a declarative model similar to SwiftUI, but with its own recomposition mechanism that, when applied incorrectly, leads to unnecessary recalculation of entire composable trees. A classic problem: a lambda function is created directly inside a composable parameter instead of being stabilized with remember, so Compose sees a new instance on every recomposition and redraws unnecessarily, even when the underlying data has not changed.
Claude specifically checks composables for missing stability: parameters that are not @Stable or @Immutable annotated types can prevent recomposition skipping, which causes noticeable performance losses in lists with many items. For LazyColumn and LazyRow, Claude consistently proposes stable key parameters, so Compose can correctly match items across list changes instead of redrawing the whole list on every change.
// OrderList.kt - stable keys and remembered callbacks for correct recomposition
@Composable
fun OrderList(
orders: List<Order>,
onOrderClick: (String) -> Unit,
) {
LazyColumn {
items(
items = orders,
key = { order -> order.id }, // stable key avoids full redraw
) { order ->
OrderRow(
order = order,
onClick = remember(order.id) { { onOrderClick(order.id) } },
)
}
}
}
@Composable
fun OrderRow(order: Order, onClick: () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(16.dp),
) {
Text(order.title, modifier = Modifier.weight(1f))
Text(order.formattedTotal)
}
}
4. Handling platform specific lifecycles correctly
iOS and Android differ fundamentally in how they handle app lifecycles: Android can terminate an activity at any time through the system and restore it later with saved state, while iOS apps typically remain fully in memory when moving to the background, but can also be terminated after prolonged inactivity. Claude consistently points out in new code when state is not saved in a way that survives, for instance via SavedStateHandle on Android or NSUserActivity and scene restoration on iOS.
A common mistake Claude finds during review is the assumption that a view model or observable class stays in the same state throughout the entire app lifecycle. On Android, a ViewModel survives configuration changes such as screen rotation, but not a process kill by the system under memory pressure. Claude therefore consistently proposes additionally saving critical state in SavedStateHandle, instead of relying solely on the view model's in memory state.
5. Shared business logic: Kotlin Multiplatform as an option
Kotlin Multiplatform, KMP for short, allows writing business logic, network layer and data models once in Kotlin and using them from both iOS and Android, while the UI layer stays native with SwiftUI and Jetpack Compose respectively. Claude helps decide which layers are suitable for KMP: network calls, serialization, caching logic and validation rules share well, while UI adjacent logic with strong platform dependency usually stays better platform specific.
When implementing this, Claude proposes encapsulating the shared layer behind a clear interface consumed from both platforms, instead of letting implementation details of the shared library leak into the native UI layer. This separation makes it easier to later implement individual parts platform specifically after all, without rebuilding the entire architecture.
6. Permissions, privacy and store policies
App Store and Play Store policies change regularly, especially around permission requests and privacy declarations, and violations reliably lead to rejection in the review process. Claude knows the current requirements for Info.plist usage descriptions on iOS, for instance that a location permission request must contain a specific, comprehensible justification instead of a generic phrasing that Apple frequently rejects during review.
On Android, Claude helps with the correct handling of runtime permissions and the distinction between normal and dangerous permissions, as well as with phrasing the privacy declarations for the Play Store data safety section. A recurring problem Claude finds during review: an app requests a permission on app start instead of only in the concrete usage context, which contradicts recommended practice and also lowers user acceptance.
7. Finding performance problems in lists and animations
Stuttering lists and animations are one of the most visible quality problems in mobile apps, and the cause in most cases lies in unnecessary work during rendering: expensive computations directly in a SwiftUI view's body instead of in a cached value, missing key parameters in Compose lists, or images loaded at full resolution even though only a small preview is needed. Claude specifically checks these patterns when reviewing list and animation code.
For image lists, Claude consistently proposes image caching and downsampling to the actually needed display size, instead of decoding large original images directly and keeping them in memory. For animations, Claude points out when an animation is computed on the main thread instead of using the platform provided, GPU accelerated animation primitives such as SwiftUI's withAnimation or Compose's animateFloatAsState.
8. Generating UI tests for SwiftUI and Compose
For SwiftUI, Claude proposes UI tests with the XCTest framework that access elements through accessibility identifiers instead of relying on visible text, which changes with localization. For Jetpack Compose, Claude recommends the ComposeTestRule with semantic matchers, which likewise work independently of the displayed language. In both cases, Claude makes sure tests wait for actual UI states rather than fixed delays, to avoid flakiness.
For business logic outside the UI layer, Claude proposes classic unit tests that run independently of the UI framework, which makes the test suite significantly faster than pure UI tests. This separation between fast unit tests for logic and slower UI tests for actual usability follows the classic test pyramid and is consistently applied by Claude in new test proposals.
// OrderListScreenTest.kt - Compose UI test with semantic matchers
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun orderList_showsOrderTitles() {
composeTestRule.setContent {
OrderList(
orders = listOf(Order(id = "1", title = "Test Order", total = 42.0)),
onOrderClick = {},
)
}
composeTestRule
.onNodeWithText("Test Order")
.assertIsDisplayed()
}
9. iOS and Android development in direct comparison
Many concepts in SwiftUI and Jetpack Compose are conceptually similar, but differ significantly in concrete implementation and typical pitfalls.
| Concept | iOS / SwiftUI | Android / Jetpack Compose | Typical pitfall |
|---|---|---|---|
| Local view state | @State |
remember { mutableStateOf(…) } |
Keeping state outside the wrapper |
| Shared view model ownership | @StateObject |
viewModel() with Hilt/Koin |
@ObservedObject instead of ownership wrapper |
| List performance | List with id |
LazyColumn with key |
Missing stable key |
| Process survival | Scene restoration | SavedStateHandle |
Assuming in memory state only |
| Permissions | Info.plist usage description |
Runtime permission dialog | Requesting on app start instead of in context |
The common denominator of both platforms is the declarative UI paradigm, which nevertheless brings different stability and ownership rules. Claude helps apply these differences correctly, instead of transferring patterns from one platform to the other without reflection.
Mironsoft
Mobile app development, SwiftUI, Jetpack Compose and AI assisted code quality
Want to establish Claude in your mobile development team?
We help with state management reviews, performance analysis for lists and animations, and architecture decisions between native and shared logic.
State review
Checking SwiftUI and Compose state management for ownership mistakes
Performance analysis
Finding recomposition and rendering problems in lists and animations
Architecture consulting
Weighing Kotlin Multiplatform versus native duplicate development on solid grounds
10. Summary
Claude supports mobile app development most effectively exactly where platform specific knowledge is decisive: correct state management with @StateObject and remember, stable recomposition in Jetpack Compose, robust handling of process kills and lifecycles, and compliance with current store policies for permissions and privacy. When correctly guided, Claude reliably distinguishes between iOS and Android specific patterns instead of transferring concepts without reflection.
The greatest practical benefit arises on recurring tasks such as new screens, performance reviews for lists, and the decision between native and shared logic via Kotlin Multiplatform. Anyone who consistently applies these patterns with Claude as a pair programming partner significantly reduces both store rejections and subtle performance problems.
Claude for Mobile App Development: the essentials at a glance
Correct state management
@StateObject vs @ObservedObject, remember with stable keys in Compose.
Secure lifecycles
SavedStateHandle and scene restoration instead of pure in memory state.
Store compliance
Specific usage descriptions and permission requests in the right context.
Choose shared logic wisely
Kotlin Multiplatform for networking and validation, UI logic stays native.