In previous posts I’ve used GalwayBus repo to illustrate results of various explorations I’ve done in to the use of Kotlin Multiplatform. However I thought there’d be value in creating a more minimal project that would allow clearer illustration of key moving parts of a multiplatform project and thus PeopleInSpace was created. It also provided opportunity to try out use of Jetpack Compose for the Android app (with UI being developed on iOS using SwiftUI - using pretty much same approach outlined in SwiftUI meets Kotlin Multiplatform!).
The project uses very basic API to show list of people currently in space (inspired by https://kousenit.org/2019/12/19/a-few-astronomical-examples-in-kotlin/)!
Note: You need to use Android Studio v4.0 (currently on Canary 6) to build Android app. Have used XCode v11.3 to build iOS app.
The Kotlin/Swift code below constitutes majority of code used in the project (I did say it was minimal!!). The project also makes use of:
- Kotlin Coroutines
- Kotlinx Serialization
- Ktor client library
- Android Architecture Components (primarily
ViewModel
) - Koin
As always, PRs or suggestions for better way of implementing any of this are very welcome (can respond to tweet shown at bottom of post)!
iOS SwiftUI Code
struct ContentView: View {
@ObservedObject var peopleInSpaceViewModel = PeopleInSpaceViewModel(repository: PeopleInSpaceRepository())
var body: some View {
NavigationView {
List(peopleInSpaceViewModel.people, id: \.name) { person in
PersonView(person: person)
}
.navigationBarTitle(Text("PeopleInSpace"), displayMode: .large)
.onAppear(perform: {
self.peopleInSpaceViewModel.fetch()
})
}
}
}
struct PersonView : View {
var person: Assignment
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(person.name).font(.headline)
Text(person.craft).font(.subheadline)
}
}
}
iOS Swift View Model
class PeopleInSpaceViewModel: ObservableObject {
@Published var people = [Assignment]()
private let repository: PeopleInSpaceRepository
init(repository: PeopleInSpaceRepository) {
self.repository = repository
}
func fetch() {
repository.fetchPeople(success: { data in
self.people = data
})
}
}
Android Jetpack Compose code
class MainActivity : AppCompatActivity() {
private val peopleInSpaceViewModel: PeopleInSpaceViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
mainLayout(peopleInSpaceViewModel)
}
}
}
@Composable
fun mainLayout(peopleInSpaceViewModel: PeopleInSpaceViewModel) {
MaterialTheme {
val people = +observe(peopleInSpaceViewModel.peopleInSpace)
Column {
people?.forEach { person ->
Row(person)
}
}
}
}
@Composable
fun Row(person: Assignment) {
Padding(16.dp) {
Text(text = "${person.name} (${person.craft})")
}
}
Android Kotlin ViewModel
class PeopleInSpaceViewModel(peopleInSpaceRepository: PeopleInSpaceRepository) : ViewModel() {
val peopleInSpace = MutableLiveData<List<Assignment>>(emptyList())
init {
viewModelScope.launch {
val people = peopleInSpaceRepository.fetchPeople()
peopleInSpace.value = people
}
}
}
Shared Kotlin Repository
It would be preferable if PeopleInSpaceApi
instance used here could also be injected using Koin. I believe there’s work ongoing to allow use of Koin
in a multiplatform project….will update this if/when that become available.
class PeopleInSpaceRepository {
private val peopleInSpaceApi = PeopleInSpaceApi()
suspend fun fetchPeople() : List<Assignment> {
val result = peopleInSpaceApi.fetchPeople()
return result.people
}
fun fetchPeople(success: (List<Assignment>) -> Unit) {
GlobalScope.launch(Dispatchers.Main) {
success(fetchPeople())
}
}
}
Shared Kotlin API Client Code (using Ktor and Kotlinx Serialization library)
@Serializable
data class AstroResult(val message: String, val number: Int, val people: List<Assignment>)
@Serializable
data class Assignment(val craft: String, val name: String)
class PeopleInSpaceApi {
private val baseUrl = "http://api.open-notify.org/astros.json"
private val client by lazy {
HttpClient() {
install(JsonFeature) {
serializer = KotlinxSerializer(Json(JsonConfiguration(strictMode = false)))
}
}
}
suspend fun fetchPeople(): AstroResult {
return client.get("$baseUrl")
}
}
Update 26/12/2019
Added commit that starts to make use of new Kotlin/Native multi-threaded coroutine support (1.3.3-native-mt
preview version)
outlined in https://github.com/Kotlin/kotlinx.coroutines/issues/462
Featured in Kotlin Weekly Issue #178
Related tweet
Just created new minimal #KotlinMultiplatform sample -https://t.co/RjvKxaMjMG. Includes use of:
— John O'Reilly (@joreilly) December 22, 2019
-SwiftUI
-Jetpack Compose
-Android ViewModel
-Koin
(article below provided inspiration for "People In Space" API I used - thanks @kenkousen!) https://t.co/GyN3HOO8e4