https://github.com/natario1/firestore
The lightweight, efficient Android wrapper for Google's Firestore model data.
https://github.com/natario1/firestore
android databinding firebase firebase-firestore firebase-object firestore model parcelable
Last synced: 2 months ago
JSON representation
The lightweight, efficient Android wrapper for Google's Firestore model data.
- Host: GitHub
- URL: https://github.com/natario1/firestore
- Owner: natario1
- License: apache-2.0
- Created: 2018-07-28T01:16:30.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2020-09-25T12:02:02.000Z (over 4 years ago)
- Last Synced: 2025-03-23T09:24:38.580Z (3 months ago)
- Topics: android, databinding, firebase, firebase-firestore, firebase-object, firestore, model, parcelable
- Language: Kotlin
- Homepage:
- Size: 214 KB
- Stars: 33
- Watchers: 2
- Forks: 6
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
[](https://github.com/natario1/Firestore/actions)
[](https://github.com/natario1/Firestore/releases)
[](https://github.com/natario1/Firestore/issues)*Need support, consulting, or have any other business-related question? Feel free to get in touch.*
*Like the project, make profit from it, or simply want to thank back? Please consider [sponsoring me](https://github.com/sponsors/natario1)!*
# Firestore
The lightweight, efficient wrapper for Firestore model data, written in Kotlin, with data-binding and Parcelable support.
```groovy
implementation 'com.otaliastudios:firestore:0.7.0'
kapt 'com.otaliastudios:firestore-compiler:0.7.0'
```- Efficient and lightweight
- Compiler to avoid reflection
- Built-in Parcelable implementation
- Built-in Data binding support
- Built-in equals & hashcode
- Written in Kotlin using map / list delegation
- Full replacement for `data class` and `Parcelize`# We know about your schema
The key classes here are `FirestoreDocument` (for documents), `FirestoreMap` and `FirestoreList` (for inner maps and lists).
They use Kotlin delegation so you can declare expected fields using the `by this` syntax:```kotlin
@FirestoreClass
class User : FirestoreDocument() {
var type: Int by this
var imageUrl: String? by this
var messages: Messages by this
@FirestoreClass
class Messages : FirestoreList()
@FirestoreClass
class Message : FirestoreMap() {
var from: String by this
var to: String by this
var text: String? by this
}
}
```The compiler will inspect your hierarchy and at runtime, it will know how to instantiate fields
and inner maps or lists, as long as you provide a no arguments constructor for them. This is valid:```kotlin
val user = User()
val message = Message()
user.messages.add(message) // We didn't have to instantiate Messages()
```Fiels are instantiated automatically and **lazily**, when requested.
The map and list implementations parsed by the compiler are also used when retrieving the document
from the network, which makes it much more efficient than using reflection to find setters.```kotlin
val user: User = documentSnapshot.toFirestoreDocument()
val lastMessage = user.messages.last()
```## Specify default values
The fields that are marked as not nullable, will be instantiated using their no arguments constructor.
This means that, for example, `Int` defaults to `0`. To specify different defaults, simply use an init block:```kotlin
@FirestoreClass
class User : FirestoreDocument() {
var type: Int by this
init {
type = UserType.ADMIN
}
}
```# We cache your documents
Similar to what Firestore-UI does, we keep a static `LruCache` of your documents based on their path.
This means that if you run a query for 50 documents and 20 were already cached, we will reuse their
instance instead of creating new ones.```kotlin
val user1: User = documentSnapshot.toFirestoreDocument()
val user2: User = documentSnapshot.toFirestoreDocument()
assert(user1 === user2)
```Of course, the fields will be updated to reflect the new network data.
# We keep some basic fields
Each object will keep (and save to network) some extra fields that are commonly used, plus have
useful functions to inspect their state with respect to the database.```kotlin
val isNew: Boolean = user.isNew() // Whether this object was saved to / comes from network
val createdAt: Timestamp? = user.createdAt // When this object was saved to network for the first time. Null if new
val updatedAt: Timestamp? = user.updatedAt // When this object was saved to network for the last time. Null if new
val reference: DocumentReference = user.getReference() // Throws if new
```We also have built-in reliable implementations for `equals()` and `hashcode()`.
# We know your dirty values
When you update some fields, either some declared field (`user.imageUrl = "url"`) or using the backing
map implementation (`user["imageUrl"] = "url"`), the document will remember that this specific field was changed
with respect to the original values.Next call to `user.save()` will internally use something like `reference.update(mapOf("imageUrl" to "url"))`,
instead of saving the whole object to the database. This is managed automatically and you don't have to worry about it.This even works with inner fields and maps!
```kotlin
user.family.father.name = "John"
user.save()// This will not send the whole User, not the whole Family and not even the whole Father.
// It will call reference.update("user.family.father.name", "John") as it should.
```# Built-in Data Binding support
The `FirestoreDocument` and `FirestoreMap` classes extend the `BaseObservable` class from the official data binding lib.
Thanks to the compiler, all declared fields will automatically call `notifyPropertyChanged()` for you,
which hugely reduce the work needed to implement databinding and two-way databinding.In fact, all you have to do is add `@get:Bindable` to your fields:
```kotlin
@FirestoreClass
class Message : FirestoreDocument() {
@get:Bindable var text: String by this
@get:Bindable var comment: String by this
}
```You can now use `message.text` and `message.comment` in XML layouts and they will be updated
when the data model changes.# We know how to `Parcel`
Documents, maps and lists implement the `Parcelable` interface.
## Local metadata
If your object holds metadata that should not by saved to network (for instance, fields that are not marked with `by this`),
they can be saved and restored to the `Parcel` overriding the class callbacks:```kotlin
@FirestoreClass
class Message : FirestoreDocument() {
var text: String by this
var comment: String by this
var hasBeenRead = false
override fun onWriteToBundle(bundle: Bundle) {
bundle.putBoolean("hasBeenRead", hasBeenRead)
}
override fun onReadFromBundle(bundle: Bundle) {
hasBeenRead = bundle.getBoolean("hasBeenRead")
}
}
```## Special types
We offer built in parcelers for `DocumentReference`, `Timestamp` and `FieldValue` types.
If your types do not implement parcelable directly, either have them implement it or register
a parceler using `FirestoreDocument.registerParceler()`:```kotlin
class App : Application() {override fun onCreate() {
registerParceler(GeoPointParceler)
registerParceler(WhateverParceler)
}
object GeoPointParceler : FirestoreDocument.Parceler() {
// ...
}
object WhateverParceler : FirestoreDocument.Parceler() {
// ...
}
}
```# Finally, we know how to update
The document class exposes `delete()`, `save()` and `trySave()` methods. They all return a `Task>` object
from the Google gms library, which you should be used to. This lets you add success and failure callbacks,
as well as chain operations with complex dependencies.## Delete
```kotlin
user.delete().addOnSuccessListener {
// User was deleted!
}.addOnFailureListener {
// Something went wrong
}
```The delete operation will throw an exception is the object `isNew()`.
## Save
The `save()` method will check if the object is new or comes from server.
- For a new object, internally this will call `reference.set()` to create your object
- For a backend object, this will call `reference.update()`. As stated above in the dirty fields chapter,
we only update exactly what was changed programmatically.
```kotlin
user.family.father = John()
user.type = User.TYPE_CHILD
user.save().addOnSuccessListener {
// User was saved!
}.addOnFailureListener {
// Something went wrong
}
```## Try Save
The `trySave()` method follows an opposite procedure.
It will first try to update the fields you specify to network, and if the call succeeds, it will update
the data document too.
```kotlin
user.family.father = null
user.trySave(
"family.father" to John(),
"type" to User.TYPE_CHILD
).addOnSuccessListener {
// User was saved!
assert(user.family.father is John)
}.addOnFailureListener {
// Something went wrong.
assert(user.family.father == null)
}
```