Android SDK limitation with subclass use-case

I would like to save 2 different objects that share some attributes in the same collection. I defined my models using the recommended pattern for such case:

@ParseClassName("BusinessAd")
open class BusinessAd: ParseObject() {
  val advertiser by ParseDelegate<Advertiser>()
  var viewsCount by intAttribute()
  var clicksCount by intAttribute()

  class Banner: BusinessAd() {
    var image by ParseDelegate<ParseFile>()
  }

  class Post: BusinessAd() {
    var description by stringAttribute()
    var attachments by ParseDelegate<List<ParseFile>>()
  }
}

But it looks like this is not possible. Because when I try to register each one of them

ParseObject.registerSubclass(BusinessAd.Banner::class.java)
ParseObject.registerSubclass(BusinessAd.Post::class.java)

I receive this error

java.lang.IllegalArgumentException: Tried to register both sy.edu.uok.admooncore.models.BusinessAd$Banner and sy.edu.uok.admooncore.models.BusinessAd$Post as the ParseObject subclass of BusinessAd. Cannot determine the right class to use because neither inherits from the other.`

I really would like to to do it this way to benifit from kotlin language features. I can still do it by define a one giant subclass but with nullable fields and then I have to manually check for the type of the objects which is not ideal

Any thoughts about this. Does it contradict with Parse concepts about how we store data or it’s legitimate use case

I tried to register only the parent class.

ParseObject.registerSubclass(BusinessAd::class.java)

But I got an error when I try to create an instance of the children classes

let instance = BusinessAd.Banner()

the error message is:

You must create this type of ParseObject using ParseObject.create() or the proper subclass.

So I tried what the error message said.

ParseObject.create(BusinessAd.Banner::class.java)

But I got an error in the places where I try to access the object (e.g. in some list adapter)

sy.edu.uok.admooncore.models.BusinessAd cannot be cast to sy.edu.uok.admooncore.models.BusinessAd$Banner

The adapter class definition and how I initialize it:

class BannerAdapter(private val banners: List<BusinessAd.Banner>, val context: Context)    
val mockedAds = listOf(
            ParseObject.create(BusinessAd.Banner::class.java)
)
binding.bannerAdsContainer.adapter = BannerAdapter(mockedAds, requireContext())

Have you tried to also annotate Banner class with @ParseClassName(“Banner”) and Post class with @ParseClassName(“Post”)?

In this case each one of them will be its own class and that’s not what is intended.
Post and Banner from parse perspective should be a BusinessAd (the fields that doesn’t exist on one of them should be undefined)

I am not sure it will work in the way you want. Let’s say later you are querying those objects, Parse has no way to know if that’s a Banner or a Post and return the right objects to you.

I really wanted to get benefit from kotlin smart cast feature. But yes as you described we will have to think about how other related parse features will deal with it.
If we have an inheritance use-case what is the best way to approach the modeling for it from parse server perspective?
An interface with multiple classes who implements it seems the way to go for me.

It feels sad that we can’t do it in a standard way in our client applications while it’s possible from the DB perspective (cuz you can store objects with different schema under the same collection)

Here is how it will look like without inheritance:

interface BusinessAd {
  val advertiser: Advertiser
  val viewsCount: Int
  val clicksCount: Int
  val viewedBy: ParseRelation<User>
  val visibleIn: List<Parent>
  val publishedDate: LocalDateTime
  val state: BusinessAd.AdState
  val subscriptionType: BusinessAd.SubscriptionType

  enum class AdState {
    DRAFT,
    PUBLIC
  }

  enum class SubscriptionType {
    BASIC,
    PREMIUM
  }
}

@ParseClassName("BannerAd")
class BannerAd : ParseObject(), BusinessAd {
  override var advertiser by ParseDelegate<Advertiser>()
  override var viewsCount by intAttribute()
  override var clicksCount by intAttribute()
  override val viewedBy by relationAttribute<User>()
  override var visibleIn by ParseDelegate<List<Parent>>()
  override var publishedDate by ParseDelegate<LocalDateTime>()
  override var state by enumAttribute<BusinessAd.AdState>()
  override var subscriptionType by enumAttribute<BusinessAd.SubscriptionType>()

  var image by ParseDelegate<ParseFile>()
}

@ParseClassName("PostAd")
class PostAd : ParseObject(),BusinessAd {
  override var advertiser by ParseDelegate<Advertiser>()
  override var viewsCount by intAttribute()
  override var clicksCount by intAttribute()
  override val viewedBy by relationAttribute<User>()
  override var visibleIn by ParseDelegate<List<Parent>>()
  override var publishedDate by ParseDelegate<LocalDateTime>()
  override var state by enumAttribute<BusinessAd.AdState>()
  override var subscriptionType by enumAttribute<BusinessAd.SubscriptionType>()

  var description by stringAttribute()
  var attachments by ParseDelegate<List<ParseFile>>()
}

2 different collections. You have to write the override logic twice in each consumer (Cuz Unfourtantly delegated properties can’t be called outside of a class)

In terms of inheritance modeling for the database, let’s say you have class A, and classes B, and C extending A, I’d go with one of the following design patterns:

  • Single class A with all fields of all classes in parse schema and a field to store the type;
  • Class A only with the fields of A (which are common to B and C), pointer to B, and pointer to C. Class B with B exclusive fields + pointer to A. Same thing for C.
  • Class B with all A and B fields, Class C with all A and C fields, and no class A in the database.