Tutorial: Serialization

Android Serialization

Webcom SDK for Android eases the serialization of business data, to be written into the Webcom database, and also their deserialization, when reading them from the Webcom database.

When coding in Kotlin

Instances of business classes may be directly written into or read from the Webcom database as soon as they are made Serializable using the official Kotlin libraries for serialization.

To do so, you have to set up this library within your project:

  • Add the dependency to the Kotlin serialization plugin into the buildscript section of your top-level build.gradle file:
buildscript {
    ext.kotlin_version = "<YOUR_KOTLIN_VERSION>"
    // ...
    dependencies {
        // ...
        classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
        // ...
    }
}
  • In the build.gradle file of your module, enable the serialization plugin:
plugins {
    // ...
    id 'kotlinx-serialization' // ADD this declaration of the Kotlin-Serialization plugin
    // ...
}
//...

Then, you can annotate your business classes as Serializable:

import kotlinx.serialization.Serializable

@Serializable // the official serialization Kotlin annotation
data class ContactDetail(
    val data: String, // String is "naturally" serializable
    val preferred: Boolean // Boolean as well
)

@Serializable
data class Contact(
    val birthday: com.orange.webcom.dessert.TimeStampDate, // this subclass of java.util.Date serializes as an integer timestamp
    val firstName: String,
    val lastName: String,
    val details: Map<String, ContactDetail>, // Map is "naturally" serializable, ContactDetail is annotated as Serializable
)

We can now directly read Contact instances from/to the Webcom database, using the get method. For further details on reading and real-time subscribing data, see the “Reading data” and “Subscribing to changes” chapters.

val manager = WebcomApplication.default.datasyncService.createManager()
manager.node("/path/to/contacts").get {
    when (it) {
        is WebcomResult.Success -> {
            when (val data = it.result.tryGet { asListOf(Contact::class) }) { // data: List<Contact>?
                null -> println("Found something else than a list of contacts")
                else -> println("Found ${data.size} contact entries")
            }
        }
        is WebcomResult.Failure -> println("Could not read contact data: ${it.error.message}")
    }
}

We can also directly write Contact instances to the Webcom database, using one of the set, merge or push methods. For further details on writing data, see the “Writing data” chapter.

val john = Contact(
    birthday = TimeStampDate(Date(1981, 12, 5)),
    firstName = "John",
    lastName = "Doe",
    details = mapOf(
        "phone" to ContactDetail("0123456789", true),
        "email" to ContactDetail("john.doe@somewhere", false)
    )
)

val manager = WebcomApplication.default.datasyncService.createManager()
manager.node("/path/to/contacts/john").set(john) {
    when (it) {
        is WebcomResult.Success -> println("Succeeded writing data")
        is WebcomResult.Failure -> println("Failed writing data")
    }
}

For more complex examples, you can also find here further details on how to set up serializable classes.

When coding in Java

In Java, of course, the Kotlin serialization framework is not available. In this case, the Webcom SDK for Android falls back to interpreting instances of business classes as Plain Old Java Objects (or POJO). In order to be considered as a POJO, the Java class of the object to serialize (or deserialize) must meet the following requirements:

  1. The class MUST NOT be an inner class,
  2. The class MUST have a default zero-arguments constructor,
  3. The class MUST define public getters/setters for the properties to serialize/deserialize, following the getMyProperty/setMyProperty naming convention.

If we simplify a little the example given in the previous section, we get:

class Contact {
    com.orange.webcom.dessert.TimeStampDate birthday;
    String firstName;
    String lastName;
    Map<String, String> details;

    // Default constructor
    public Contact() {
    }

    // Getter and setter for the "birthday" property
    public TimeStampDate getBirthday() {
        return birthday;
    }

    public Contact setBirthday(TimeStampDate birthday) {
        this.birthday = birthday;
        return this;
    }

    // Getter and setter for the "firstName" property
    public String getFirstName() {
        return firstName;
    }

    public Contact setFirstName(String firstName) {
        this.firstName = firstName;
        return this;
    }

    // Getter and setter for the "lastName" property
    public String getLastName() {
        return lastName;
    }

    public Contact setLastName(String lastName) {
        this.lastName = lastName;
        return this;
    }

    // Getter and setter for the "details" property
    public Map<String, String> getDetails() {
        return details;
    }

    public Contact setDetails(Map<String, String> details) {
        this.details = details;
        return this;
    }
}

Then the Kotlin code snippets, as well as their Java counterparts, shown in the previous section may then be used the same way!

When using POJO classes, do not forget to protect them against shrinking and obfuscation when building APKs with proguard/R8. Indeed, as serialization and deserialization of POJO classes rely on Java reflection, their default constructors, getters and setters must be preserved.

There are 2 ways to do so:

  • Either annotate POJO classes with androidx.annotation.Keep,
  • Or add a customized rule for each POJO class in the proguard-rules.pro file of the corresponding module:
    -keepclassmembers class fqcn.of.pojo.class { public <init>(); public void set*(**); public *** get*(); }