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 constitues 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