Minimal Kotlin Multiplatform project using Compose and SwiftUI

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:

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

Share: Twitter LinkedIn
Morty Proxy This is a proxified and sanitized view of the page, visit original site.