Skip to content

Type-safe API

Dependencies

Add repository URL https://s01.oss.sonatype.org/content/repositories/snapshots/ for SNAPSHOT versions.

plugins {
    kotlin("plugin.serialization")
}

dependencies {
    implementation("io.firestore4k:typed-api:$latestVersion")
}

Model

Example

In the sample code below, we will define model for:

/users/{useri-d}/messages/{message-id}

So, we have 2 collections - users and messages, each of the collection have documents under them.

users will be a root collection.
messages will be a sub collection under users.

Entity classes

  • Define Kotlin data classs for the firestore documents.

Important

Annotate them with @kotlinx.serialization.Serializable

@kotlinx.serialization.Serializable
data class User(
    val name: String,
    val email: String,
)

@kotlinx.serialization.Serializable
data class Message(
    val from: String,
    val to: String,
    val subject: String,
    val body: String,
)

Identity classes (optional)

Define Kotlin value classs for IDs for type-safety.

Note

This is optional, in which case, you may use String as IDs.

Important

override fun toString() to return the string id value.

@JvmInline
value class UserId(private val value: String) {
    override fun toString(): String = value
}

@JvmInline
value class MessageId(private val value: String) {
    override fun toString(): String = value
}

Collections

  • Define root collection - users.
  • data class User will be used for Document schema (fields).
  • value class UserId will be used as Identity class. You may use String instead too.
  • "users" will be used in firebase path.
    val users = rootCollection<User, UserId>("users")
    
  • Define a sub collection - messages under users.
  • Similarly, for messages, Message is the entity class and MessageId its identity class.
    val messages = users.subCollection<Message, MessageId>("messages")
    

firestore path expression

DSL to express Firestore collection & document path.

val users = rootCollection<User, UserId>("users")
val messages = users.subCollection<Message, MessageId>("messages")

// /users
users

// /users/user1
users / UserId("user1")

// /users/user1/message
users / UserId("user1") / messages

// /users/user1/message/message1
users / UserId("user1") / messages / MessageId("message1")

CRUD operations

Use collection & document path for operations.

add

// add (ID auto generated by Firestore)
// add(<collection path>, <document object>): <String Id>
val userId: String = add(users, User())

put

// put (create or update)
// put(<document path>, <document object>)
put(users / UserId("user1"), User())

get

// get
// get(<document path>): <document object>
val user = get<User>(users / UserId("user1"))

get all

// get all
// getAll(<collection path>): Collection<document object>
val messages = getAll<Message>(users / UserId("user1") / messages)

delete document

// delete document and its child entities
// This can be a document under root collection or sub-collection.

// deleteDocument(<document path>)
deleteDocument(users / UserId("user1"))

delete collection

// delete collection and its child entities.
// This can be root collection or sub-collection.

// deleteCollection(<collection path>)
deleteCollection(users)

Future improvements

  • Optional typesafe value class as Identity class for documents, instead of String.
  • Have single method to delete collection and document.
  • Non-recursive delete options.
  • add to return id as Identity class instead of String.
  • Scope or context for operations enabling -
    • option to use relative path instead of entire path from root.
    • CRUD operation on document fields.
    • Query filters
  • Transactions
  • Custom FirestoreOptions.