Skip to content

Commit 8fa235b

Browse files
authored
Merge pull request #1 from PureSwift/compose-listview
Compose-backed List, JNI import fixes, Fragment mount handling
2 parents 9b94d73 + 2d6e304 commit 8fa235b

13 files changed

Lines changed: 108 additions & 60 deletions

‎.swift-version‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
6.3.2

‎Demo/app/build.gradle.kts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ dependencies {
7676
implementation(libs.androidx.ui.graphics)
7777
implementation(libs.androidx.ui.tooling.preview)
7878
implementation(libs.androidx.material3)
79+
implementation(libs.androidx.foundation)
7980
testImplementation(libs.junit)
8081
androidTestImplementation(libs.androidx.junit)
8182
androidTestImplementation(libs.androidx.espresso.core)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.pureswift.swiftandroid
2+
3+
import android.content.Context
4+
import android.view.ViewGroup
5+
import android.widget.FrameLayout
6+
import androidx.compose.foundation.layout.padding
7+
import androidx.compose.foundation.lazy.LazyColumn
8+
import androidx.compose.material3.Text
9+
import androidx.compose.runtime.getValue
10+
import androidx.compose.runtime.key
11+
import androidx.compose.runtime.mutableIntStateOf
12+
import androidx.compose.runtime.setValue
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.platform.ComposeView
15+
import androidx.compose.ui.unit.dp
16+
import com.pureswift.swiftandroid.ui.theme.SwiftAndroidTheme
17+
18+
// SwiftUI `List` backed by a Jetpack Compose `LazyColumn`, sourcing its rows from a Swift-implemented `ListViewAdapter`.
19+
// `ComposeView` is final, so it's hosted as a child of this `FrameLayout` rather than subclassed.
20+
class ComposeListView(context: Context, private val adapter: ListViewAdapter) : FrameLayout(context) {
21+
22+
private var version by mutableIntStateOf(0)
23+
24+
init {
25+
val composeView = ComposeView(context)
26+
composeView.setContent {
27+
SwiftAndroidTheme {
28+
key(version) {
29+
LazyColumn {
30+
items(adapter.getCount()) { index ->
31+
Text(
32+
text = adapter.getItem(index) as String,
33+
modifier = Modifier.padding(16.dp)
34+
)
35+
}
36+
}
37+
}
38+
}
39+
}
40+
addView(composeView, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
41+
}
42+
43+
fun getAdapter(): ListViewAdapter = adapter
44+
45+
fun refresh() {
46+
version++
47+
}
48+
}

‎Demo/gradle/libs.versions.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
2424
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
2525
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
2626
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
27+
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation" }
2728

2829
[plugins]
2930
android-application = { id = "com.android.application", version.ref = "agp" }

‎Sources/AndroidSwiftUI/AndroidListView.swift‎

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import AndroidKit
99

10-
/// SwitUI View for Android `android.widget.ListView`
10+
/// SwiftUI `List` for Android, backed by a Jetpack Compose `LazyColumn`.
1111
public struct AndroidListView {
1212

1313
let items: [String]
@@ -18,47 +18,20 @@ public struct AndroidListView {
1818
}
1919

2020
extension AndroidListView: AndroidViewRepresentable {
21-
21+
2222
/// Creates the view object and configures its initial state.
23-
public func makeAndroidView(context: Self.Context) -> ListView {
24-
createView(context: context.androidContext)
25-
}
26-
27-
/// Updates the state of the specified view with new information from SwiftUI.
28-
public func updateAndroidView(_ view: ListView, context: Self.Context) {
29-
updateView(view)
23+
public func makeAndroidView(context: Self.Context) -> ComposeListView {
24+
let adapter = ListViewAdapter(swiftObject: SwiftObject(ListViewAdapter.Context(items: items)))
25+
return ComposeListView(context.androidContext, adapter)
3026
}
31-
}
3227

33-
extension AndroidListView {
34-
35-
func createView(context: AndroidContent.Context) -> AndroidWidget.ListView {
36-
let view = AndroidWidget.ListView(context)
37-
updateView(view)
38-
return view
39-
}
40-
41-
func updateView(_ view: AndroidWidget.ListView) {
42-
let layout = try! JavaClass<R.layout>()
43-
let resource = layout.simple_list_item_1
44-
let objects: [JavaObject?] = items.map { JavaString($0) }
45-
let adapter = ArrayAdapter<JavaObject>(
46-
context: view.getContext(),
47-
resource: resource,
48-
objects: objects
49-
)
50-
view.setAdapter(adapter.as(Adapter.self))
28+
/// Updates the state of the specified view with new information from SwiftUI.
29+
public func updateAndroidView(_ view: ComposeListView, context: Self.Context) {
30+
guard let adapter = view.getAdapter() else {
31+
assertionFailure("Missing adapter")
32+
return
33+
}
34+
adapter.context = ListViewAdapter.Context(items: items)
35+
view.refresh()
5136
}
5237
}
53-
54-
extension JavaClass<R.layout> {
55-
56-
@JavaStaticField(isFinal: true)
57-
public var list_view_row: Int32
58-
}
59-
60-
extension JavaClass<R.id> {
61-
62-
@JavaStaticField(isFinal: true)
63-
public var textView: Int32
64-
}

‎Sources/AndroidSwiftUI/AndroidRenderer.swift‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
// Created by Alsey Coleman Miller on 6/8/25.
66
//
77

8-
import JavaKit
98
import AndroidKit
109

1110
final class AndroidRenderer: Renderer {
@@ -74,6 +73,10 @@ final class AndroidRenderer: Renderer {
7473
viewGroup.addView(viewObject)
7574
log("\(self).\(#function) \(#line): Add \(viewObject.getClass().getName()) to \(viewGroup.getClass().getName())")
7675
return AndroidTarget(host.view, viewObject)
76+
case .fragment:
77+
// TODO: Mount into a fragment via FragmentManager/FragmentTransaction
78+
log("\(self).\(#function) \(#line) Fragment mounting not yet implemented")
79+
return nil
7780
}
7881
} else {
7982

@@ -106,6 +109,9 @@ final class AndroidRenderer: Renderer {
106109
case .view(let view):
107110
log("\(self).\(#function) Update \(view.getClass().getName())")
108111
widget.updateAndroidView(view)
112+
case .fragment:
113+
// TODO: Update fragment
114+
break
109115
}
110116
}
111117

‎Sources/AndroidSwiftUI/AndroidTarget.swift‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
// Created by Alsey Coleman Miller on 6/8/25.
66
//
77

8-
import JavaKit
98
import AndroidKit
109

1110
final class AndroidTarget: Target {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// ComposeListView.swift
3+
// AndroidSwiftUI
4+
//
5+
// Created by Alsey Coleman Miller on 7/16/26.
6+
//
7+
8+
import AndroidKit
9+
10+
/// SwiftUI `List` backed by a Jetpack Compose `LazyColumn`.
11+
@JavaClass("com.pureswift.swiftandroid.ComposeListView")
12+
open class ComposeListView: AndroidView.View {
13+
14+
@JavaMethod
15+
@_nonoverride public convenience init(_ context: AndroidContent.Context?, _ adapter: ListViewAdapter?, environment: JNIEnvironment? = nil)
16+
17+
@JavaMethod
18+
open func getAdapter() -> ListViewAdapter?
19+
20+
@JavaMethod
21+
open func refresh()
22+
}

‎Sources/AndroidSwiftUI/Fragment.swift‎

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,28 +58,27 @@ public extension Fragment {
5858
}
5959

6060
extension Fragment {
61-
62-
static var logTag: LogTag { "Fragment" }
63-
61+
62+
static var logTag: String { "Fragment" }
63+
64+
static let log = try! JavaClass<AndroidUtil.Log>()
65+
6466
static func log(_ string: String) {
65-
try? AndroidLogger(tag: logTag, priority: .debug)
66-
.log(string)
67+
_ = Self.log.d(Self.logTag, string)
6768
}
68-
69+
6970
static func logInfo(_ string: String) {
70-
try? AndroidLogger(tag: logTag, priority: .info)
71-
.log(string)
71+
_ = Self.log.i(Self.logTag, string)
7272
}
73-
73+
7474
static func logError(_ string: String) {
75-
try? AndroidLogger(tag: logTag, priority: .error)
76-
.log(string)
75+
_ = Self.log.e(Self.logTag, string)
7776
}
78-
77+
7978
func log(_ string: String) {
8079
Self.log(string)
8180
}
82-
81+
8382
func logError(_ string: String) {
8483
Self.logError(string)
8584
}

‎Sources/AndroidSwiftUI/ImageView.swift‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created by Alsey Coleman Miller on 6/9/25.
66
//
77

8+
import Foundation
89
import AndroidKit
910

1011
extension Image: AnyAndroidView {

0 commit comments

Comments
 (0)