SQLDelight convert query return type when using flows

1.7k Views Asked by At

I want to use SQLDelight as a caching layer in my App with the coroutines extension to return a flow from my SQL queries and get notified when the entry in the local Database changes.
But because SQLDelight generates it's own class for the stored entity and emits them in a flow I'm having trouble converting the stored class to the class used throughout the rest of my app.

Below you can find an extract of my FriendEntity SQL type and query function which SQLDelight uses to generate the FriendEntity data class and kotlin functions (Generated outputs at bottom of Question)

// SQLDelight queries
CREATE TABLE FriendEntity (
        id TEXT NOT NULL PRIMARY KEY,
        username TEXT NOT NULL,
        firstname TEXT NOT NULL,
        lastname TEXT,
        phone TEXT,
        picture TEXT,
        accepted INTEGER AS Boolean DEFAULT 0 NOT NULL

getFriendById:
SELECT * FROM FriendEntity
WHERE id = :id;
);

Below I want to create a caching service which also emits a flow but of type Friend and not FriendEntity so I somehow have to convert the FriendEntity class to my Friend class while still returning a flow.
Is that even possible without collecting the flow first?


override fun get(id: String): Flow<Friend>? {
    return try {
        return queries.getFriendById(id = id).asFlow() //returns Flow<Query<FriendEntity>>
    } catch (e: NullPointerException) {
        null
    }
}

data class Friend(
    var profile: Profile,
    var accepted: Boolean
)

data class Profile(
    var id: String,
    var username: String,
    var firstname: String,
    var lastname: String?,
    var phone: String? = null,
    var picture: String? = null,
)

Generated By SQLDelight:

public fun <T : Any> getFriendById(id: String, mapper: (
    id: String,
    username: String,
    firstname: String,
    lastname: String?,
    phone: String?,
    picture: String?,
    accepted: Boolean
  ) -> T): Query<T>

  public fun getFriendById(id: String): Query<FriendEntity>
public data class FriendEntity(
  public val id: String,
  public val username: String,
  public val firstname: String,
  public val lastname: String?,
  public val phone: String?,
  public val picture: String?,
  public val accepted: Boolean
) {
  public override fun toString(): String = """
  |FriendEntity [
  |  id: $id
  |  username: $username
  |  firstname: $firstname
  |  lastname: $lastname
  |  phone: $phone
  |  picture: $picture
  |  accepted: $accepted
  |]
  """.trimMargin()
}
2

There are 2 best solutions below

0
On BEST ANSWER

I asked this question in their Github discussion and got a great answer that doesn't depend on an extension. You can use a custom mapper parameter when calling the query:

override fun get(id: Long): Flow<Query<Friend>>? {
  return try {
      return queries.getFriendById(
          id = id,
          mapper = { friendId, username, firstname, lastname, phone, picture, accepted ->
              Friend(
                  Profile(friendId, username, firstname, lastname, phone, picture),
                  accepted
              )
          }).asFlow()
  } catch (e: NullPointerException) {
      null
  }
}

CC: Alec Strong
https://github.com/cashapp/sqldelight/discussions/2782

0
On

You have to use this extension implementation in your source set.

kotlin {
  sourceSets.commonMain.dependencies {
    implementation "com.squareup.sqldelight:coroutines-extensions:1.5.3"
  }
}

Now you can get the data like

val data: Flow<List<//YourDataClass>> = 
  query.selectAll()
    .asFlow()
    .mapToList()

Ref: SQLDelight with flow