0

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);
Niels
  • 469
  • 2
  • 6
  • 18

1 Answers1

1

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).

SUPERCILEX
  • 3,929
  • 4
  • 32
  • 61
  • Just to make sure i understand it: `var phones: Map = mapOf()` stores the Collection `phones` and i could access the actual `Phone` object via `phoneObjects`. What i am confused about is the `@Exclude` annotation. Wouldn't this prevent the respective properties from beeing stored in Firestore? – Niels Mar 20 '18 at 12:44
  • Yeah, you got it. About the annotations, do you use proguard? If not, you don't need any of them. Otherwise, the exclude annotations are there to ensure your fields don't accidentally get added to Firebase since proguard is allowed to make them public. And the keep ones only apply to the getters and setters. I was just trying to save you the pain of going "What!? Why are my release builds crashing?" – SUPERCILEX Mar 20 '18 at 16:20