Search code examples
androidfirebasekotlingoogle-cloud-firestorefirebaseui

Managing custom models with list attributes as documents with subcollections


I have a Contact model which holds different attributes and lists of attributes. Now i want to store these in Firestore but with the lists as subcollections of the respective document.

A minimal example of the Contact model looks like this (note that the list is excluded from the map):

class Contact(private val map: MutableMap<String, Any?>){
    var nameDisplay:String? by map
    var nickname:String? by map
    var organisation:String? by map
    val phones:ArrayList<Phone> = ArrayList()
}

With this representation in Firestore:

contacts:
    contact1
        nameDisplay = "Test"
        nickname = "Test"
        organisation = "some corp"
        phones:
            phone1
                number = 8243525
                label = "this guy"
            phone2
                number = 8243525433
                label = "this guy"
    contact2
        ....

I am looking for a way to best implement this behavior.

A working solution i found is with a secondary constructor where i can pass the collections seperately.

A desired implementation would be the default implementation for custom objects but with the above behavior:

var contact: Contact = documentSnapshot.toObject(Contact::class.java);

Solution

  • I think you're over engineering things. 😂 It would be much better Kotlin to have those properties be initialized from the constructor through parameters rather than the crazy delegation you're doing. (Though I must applaud you for pushing the language to its limits! 👍) As it so happens, the toObject method works very nicely with those best practices. So your Contact class should look something like this:

    data class Contact(
            @Exclude
            @get:Keep
            @set:Keep
            var nameDisplay: String? = null,
    
            @Exclude
            @get:Keep
            @set:Keep
            var nickname: String? = null,
    
            @Exclude
            @get:Keep
            @set:Keep
            var organisation: String? = null,
    
            @Exclude
            @get:Keep
            @set:Keep
            @set:RestrictTo(RestrictTo.Scope.TESTS) // Hack b/c Firebase needs a setter but we don't
            var phones: Map<String, Any?> = mapOf()
    ) {
        val phoneObjects: MutableList<User> by lazy {
            phones.parse() // Map them to your phone objects
        }
    }
    

    That's a bit ugly but lets you use the toObject method. A version with more code but that makes for a prettier data class would be manually passing in the parameters with snapshot.getString(...). However, if you really want to go the delegation route, I'm pretty sure you could just delegate the phones property to the map as well and then just pass in the snapshot data: Contact(snapshot.data).