4

I'm trying to use the new @Parcelize annotation added with Kotlin 1.1.4 with a Realm objet containing a RealmList attribute.

@Parcelize
@RealmClass
open class Garage(
        var name: String? = null,
        var cars: RealmList<Car> = RealmList()
) : Parcelable, RealmModel

As RealmList is not supported by the annotation and assuming that @Parcelize is there specially to avoid creating methods what would be the solution to support RealmList ?

Thanks in advance

1 Answers1

8

EDIT:

Using the power of Type Parcelers and @WriteWith annotation, it is possible to create a RealmList type parceler that can handle this scenario for you:

With the following code:

fun <T> Parcel.readRealmList(clazz: Class<T>): RealmList<T>?
    where T : RealmModel,
          T : Parcelable = when {
    readInt() > 0 -> RealmList<T>().also { list ->
        repeat(readInt()) {
            list.add(readParcelable(clazz.classLoader))
        }
    }
    else -> null
}

fun <T> Parcel.writeRealmList(realmList: RealmList<T>?, clazz: Class<T>)
    where T : RealmModel,
          T : Parcelable {
    writeInt(when {
        realmList == null -> 0
        else -> 1
    })
    if (realmList != null) {
        writeInt(realmList.size)
        for (t in realmList) {
            writeParcelable(t, 0)
        }
    }
}

You can define an interface like this:

interface RealmListParceler<T>: Parceler<RealmList<T>?> where T: RealmModel, T: Parcelable {
    override fun create(parcel: Parcel): RealmList<T>? = parcel.readRealmList(clazz)

    override fun RealmList<T>?.write(parcel: Parcel, flags: Int) {
        parcel.writeRealmList(this, clazz)
    }

    val clazz : Class<T>
}

Where you'll need to create a specific parceler for the RealmList<Car> like this:

object CarRealmListParceler: RealmListParceler<Car> {
    override val clazz: Class<Car>
        get() = Car::class.java
}

but with that, now you can do the following:

@Parcelize
@RealmClass
open class Garage(
        var name: String? = null,
        var cars: @WriteWith<CarRealmListParceler> RealmList<Car> = RealmList()
) : Parcelable, RealmModel

And

@Parcelize
@RealmClass
open class Car(
    ..
): RealmModel, Parcelable {
    ...
}

This way you don't need to manually write the Parceler logic.



ORIGINAL ANSWER:

Following should work:

@Parcelize
open class Garage: RealmObject(), Parcelable {
    var name: String? = null
    var cars: RealmList<Car>? = null

    companion object : Parceler<Garage> {
        override fun Garage.write(parcel: Parcel, flags: Int) {
            parcel.writeNullableString(name)
            parcel.writeRealmList(cars)
        }

        override fun create(parcel: Parcel): Garage = Garage().apply {
            name = parcel.readNullableString()
            cars = parcel.readRealmList()
        }
    }
}

As long as you also add following extension functions:

inline fun <reified T> Parcel.writeRealmList(realmList: RealmList<T>?)
    where T : RealmModel,
          T : Parcelable {
    writeInt(when {
        realmList == null -> 0
        else -> 1
    })
    if (realmList != null) {
        writeInt(realmList.size)
        for (t in realmList) {
            writeParcelable(t, 0)
        }
    }
}

inline fun <reified T> Parcel.readRealmList(): RealmList<T>?
    where T : RealmModel,
          T : Parcelable = when {
    readInt() > 0 -> RealmList<T>().also { list ->
        repeat(readInt()) {
            list.add(readParcelable(T::class.java.classLoader))
        }
    }
    else -> null
}

and also

fun Parcel.writeNullableString(string: String?) {
    writeInt(when {
        string == null -> 0
        else -> 1
    })
    if (string != null) {
        writeString(string)
    }
}

fun Parcel.readNullableString(): String? = when {
    readInt() > 0 -> readString()
    else -> null
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • Amazing, you're amazing ! I'm implementing it right now. I just have a question. We won't have access to `cars` or `name ` variables in the companion object. How to solve this ? – Alexandre Nussbaumer Mar 23 '18 at 19:05
  • You do because Parceler gives you `Garage.write` and otherwise you are creating a new instance of it (i kinda had a copy paste error there a sec ago) --- but I just realized [I can probably also improve this answer a bit using a `Parceler>` with `@WriteWith()`](https://kotlinlang.org/docs/tutorials/android-plugin.html) - assuming I can get its generic to work, anyways – EpicPandaForce Mar 23 '18 at 19:07
  • Hmm I can't seem to make `object RealmListParceler:` because `` is not allowed on object, not sure if I can make it better then. – EpicPandaForce Mar 23 '18 at 19:14
  • What I meant is your code won't compile because the variable `cars` in `parcel.writeRealmList(cars)` is not accessible within the companion object. – Alexandre Nussbaumer Mar 23 '18 at 19:14
  • Yes it is accessible, because it is `Garage.write(`. – EpicPandaForce Mar 23 '18 at 19:15
  • Oh sorry, did't saw it – Alexandre Nussbaumer Mar 23 '18 at 19:16
  • Thanks a lot you're amazing. It's working like a charm ! – Alexandre Nussbaumer Mar 23 '18 at 19:30
  • @AlexandreNussbaumer thanks for accepted, however this way you need to write Parcelable implementation (albeit nicer). Can you try the edited answer if that also works (it's at the top)? I was tinkering with that for the past 20 mins and I think it should work too. – EpicPandaForce Mar 23 '18 at 19:33
  • I really like your new cleaner implementation. Just added your code but it doesn't compile. Actually in your interface ´RealmListParceler: Parceler?>´, ´RealmList?´ is not within its bounds, it should be a ´Parcelable´ and for the ´@WriteWith´, it says PARCELABLE_TPYPE_NOT_SUPPORTED for ´@Parcelable´ do you have any suggestions ? – Alexandre Nussbaumer Mar 30 '18 at 15:24
  • Both `Car` and `Garage` must be Parcelable. Also, I had to make the `Parcel.write...` and `Parcel.read...` extension functions be top-level functions, along with the interface and the object. Also, there is no such thing as `@Parcelable`, it's `@Parcelize`..... But I've just added this into my test project for this `var parents: @WriteWith RealmList = RealmList()` and it works, so I think you are missing a `>` somewhere – EpicPandaForce Mar 30 '18 at 19:20
  • So I tried out what I wrote and it worked, I can't tell from a distance what went wrong on your side :| – EpicPandaForce Apr 01 '18 at 18:00
  • 1
    I implemented your logic again and it does work now, I don't know why it wasn't building before. Again, many thanks! – Alexandre Nussbaumer Jun 15 '18 at 16:49
  • @EpicPandaForce thanks for this. I am using it but now am getting multiple errors in writeToParcel(Unknown Source:251). I think it's coming from a model using LinkingObjects. How do I exclude LinkingObjects from the parcel? I am using Kotlin Parcelize – saintjab Aug 10 '19 at 16:25