So, in previous post (Minimal Kotlin Multiplatform project using Compose and SwiftUI) I gave brief overview of PeopleInSpace, a small repository that was designed to illustrate key moving parts of a Kotlin Multiplatform project (running on Android, iOS, and later, watchOS). However, although I’m reluctant to add much else to the project, one key gap was around locally persisting the data we got back from remote server. The somewhat serendipitous combination of following activities in last few weeks also provided further encouragement to add such functionality to the project (1) Neal’s addition of watchOS support to PeopleInSpace, and (2) watchOS support was added to SQLDelight (Kotlin Multiplatform persistence library).

I’ve described usage of SQLDelight in a previous post (Introduction to Multiplatform Persistence with SQLDelight) and this duplicates some of the information in there but I felt it was still useful to see this again in slightly different context…and also, importantly, we can now take advantage of upcoming Kotlin Native multi-threaded coroutines support (along with SQLDelight’s Flow support)

Adding SQLDelight support

So, changes to add SQLDelight to project were made in following commit. We start by adding use of com.squareup.sqldelight plugin to shared module build.gradle along with associated sqldelight configuration. We also added dependency to com.squareup.sqldelight libraries for various platforms - note use since 1.2.2 release of com.squareup.sqldelight:native-driver for native (e.g. iOS/watchOS) targets.

sqldelight {
    PeopleInSpaceDatabase {
        packageName = "com.surrus.peopleinspace.db"
        sourceFolders = ["sqldelight"]
    }
}

This is combined with PeopleInSpace.sq to automatically generate persistence code needed (in particular PeopleInSpaceDatabase and PeopleInSpaceQueries)

CREATE TABLE People(
name TEXT NOT NULL PRIMARY KEY,
craft TEXT NOT NULL
);

insertItem:
INSERT OR REPLACE INTO People(name, craft)VALUES(?,?);

selectAll:
SELECT * FROM People;

We can now make use of generated code mentioned above to add persistence functionality to PeopleInSpaceRepository (Note use of SQLDelight’s asFlow() method). We use expect/actual to allow provision of versions of createDb() specific to each platform (see repository for details).

expect fun createDb() : PeopleInSpaceDatabase

class PeopleInSpaceRepository {
    private val peopleInSpaceApi = PeopleInSpaceApi()
    private val peopleInSpaceDatabase = createDb()
    private val peopleInSpaceQueries = peopleInSpaceDatabase.peopleInSpaceQueries

    init {
        GlobalScope.launch (Dispatchers.Main) {
            fetchAndStorePeople()
        }
    }

    fun fetchPeopleAsFlow()  = peopleInSpaceQueries.selectAll(mapper = { name, craft ->
            Assignment(name = name, craft = craft)
        }).asFlow().mapToList()

    suspend fun fetchAndStorePeople()  {
        val result = peopleInSpaceApi.fetchPeople()
        result.people.forEach {
            peopleInSpaceQueries.insertItem(it.name, it.craft)
        }
    }

    // called from iOS/watchOS client
    fun fetchPeople(success: (List<Assignment>) -> Unit) {
        GlobalScope.launch(Dispatchers.Main) {
            fetchPeopleAsFlow().collect {
                success(it)
            }
        }
    }
}

Note there’s currently an issue running this in Watch Simulator (see this tweet for more details….will post update here when that’s resolved).

Featured in Kotlin Weekly Issue #183