Skip to content Skip to sidebar Skip to footer

Workmanager Data.builder Does Not Support Parcelable

When you have a big POJO with loads of variables (Booleans, Int, Strings) and you want to use the new Work Manager to start a job. You then create a Data file which gets added to t

Solution 1:

Super easy with GSON: https://stackoverflow.com/a/28392599/5931191

// Serialize a single object.    publicStringserializeToJson(MyClass myClass) {
    Gson gson = newGson();
    String j = gson.toJson(myClass);
    return j;
}
// Deserialize to single object.publicMyClassdeserializeFromJson(String jsonString) {
    Gson gson = newGson();
    MyClass myClass = gson.fromJson(jsonString, MyClass.class);
    return myClass;
}

Solution 2:

I'm posting my solution here as I think it might be interesting for other people. Note that this was my first go at it, I am well aware that we could probably improve upon it, but this is a nice start.

Start by declaring an abstract class that extends from Worker like this:

abstractclassSingleParameterWorker<T> : Worker(), WorkManagerDataExtender{

    finaloverridefundoWork(): WorkerResult {
        return doWork(inputData.getParameter(getDefaultParameter()))
    }

    abstractfundoWork(t: T): WorkerResult

    abstractfungetDefaultParameter(): T
}

The WorkManagerDataExtender is an interface that has extensions to Data. getParameter() is one of these extensions:

fun<T> Data.getParameter(defaultValue: T): T {
    returnwhen (defaultValue) {
        is ClassA-> getClassA() as T
        is ClassB-> getClassB() as T
        ...
        else -> defaultValue
    }
}

Unfortunately I was not able to use the power of inlined + reified to avoid all the default value logic. If someone can, let me know in the comments. getClassA() and getClassB() are also extensions on the same interface. Here is an example of one of them:

fun Data.getClassA(): ClassA {
        val map = keyValueMap
        return ClassA(map["field1"] as String,
                map["field2"] asInt,
                map["field3"] as String,
                map["field4"] asLong,
                map["field5"] as String)
    }

fun ClassA.toMap(): Map<String, Any> {
        return mapOf("field1" to field1,
                "field2" to field2,
                "field3" to field3,
                "field4" to field4,
                "field5" to field5)
    }

(Then you can call toWorkData() on the return of this extension, or make it return Data instead, but this way you can add more key value pairs to the Map before calling toWorkData()

And there you go, now all you have to do is create subclasses of the SingleParameterWorker and then create "to" and "from" extensions to Data to whatever class you need. In my case since I had a lot of Workers that needed the same type of POJO, it seemed like a nice solution.

Solution 3:

This solution works without using JSON, and serializes directly to byte array.

package com.andevapps.ontv.extension

import android.os.Parcel
import android.os.Parcelable
import androidx.work.Data
import java.io.*

fun Data.Builder.putParcelable(key: String, parcelable: Parcelable): Data.Builder {
    val parcel = Parcel.obtain()
    try {
        parcelable.writeToParcel(parcel, 0)
        putByteArray(key, parcel.marshall())
    } finally {
        parcel.recycle()
    }
    returnthis
}

fun Data.Builder.putParcelableList(key: String, list: List<Parcelable>): Data.Builder {
    list.forEachIndexed { i, item ->
        putParcelable("$key$i", item)
    }
    returnthis
}

fun Data.Builder.putSerializable(key: String, serializable: Serializable): Data.Builder {
    ByteArrayOutputStream().use { bos ->
        ObjectOutputStream(bos).use { out ->
            out.writeObject(serializable)
            out.flush()
        }
        putByteArray(key, bos.toByteArray())
    }
    returnthis
}

@Suppress("UNCHECKED_CAST")inlinefun<reified T : Parcelable> Data.getParcelable(key: String): T? {
    val parcel = Parcel.obtain()
    try {
        val bytes = getByteArray(key) ?: returnnull
        parcel.unmarshall(bytes, 0, bytes.size)
        parcel.setDataPosition(0)
        val creator = T::class.java.getField("CREATOR").get(null) as Parcelable.Creator<T>
        return creator.createFromParcel(parcel)
    } finally {
        parcel.recycle()
    }
}

inlinefun<reified T : Parcelable> Data.getParcelableList(key: String): MutableList<T> {
    val list = mutableListOf<T>()
    with(keyValueMap) {
        while (containsKey("$key${list.size}")) {
            list.add(getParcelable<T>("$key${list.size}") ?: break)
        }
    }
    return list
}

@Suppress("UNCHECKED_CAST")fun<T : Serializable> Data.getSerializable(key: String): T? {
    val bytes = getByteArray(key) ?: returnnull
    ByteArrayInputStream(bytes).use { bis ->
        ObjectInputStream(bis).use { ois ->
            return ois.readObject() as T
        }
    }
}

Add proguard rule

-keepclassmembers class * implementsandroid.os.Parcelable{
  publicstaticfinal android.os.Parcelable$Creator CREATOR;
}

Solution 4:

Accepted answer is correct. But new android developer can not understand easily, So thats why i given another answer with proper explanation.

My Requirement is pass Bitmap object. (You can pass as per your requirement)

Add dependency in your gradle file

Gradle:

dependencies {
  implementation 'com.google.code.gson:gson:2.8.5'
}

Use below method for serialize and de-serialize object

// Serialize a single object.publicstaticStringserializeToJson(Bitmap bmp) {
        Gson gson = newGson();
        return gson.toJson(bmp);
    }

    // Deserialize to single object.publicstaticBitmapdeserializeFromJson(String jsonString) {
        Gson gson = newGson();
        return gson.fromJson(jsonString, Bitmap.class);
    }

Serialize object.

StringbitmapString= Helper.serializeToJson(bmp);

Pass to data object.

 Data.Builder builder = new Data.Builder();
 builder.putString("bmp, bitmapString);
 Data data = builder.build();
        OneTimeWorkRequest simpleRequest = new OneTimeWorkRequest.Builder(ExampleWorker.class)
                .setInputData(data)
                .build();
        WorkManager.getInstance().enqueue(simpleRequest);

Handle your object in your Worker class.

Datadata= getInputData();
StringbitmapString= data.getString(NOTIFICATION_BITMAP);
Bitmapbitmap= Helper.deserializeFromJson(bitmapString);

Now your bitmap object is ready in Worker class.

Above is example, how to pass object in your worker class.

Solution 5:

In Kotlin, thats how I do it

Object to Json

inlinefun Any.convertToJsonString():String{
 return Gson().toJson(this)?:""
}

To Convert back to model,

inlinefun<reified T> JSONObject.toModel(): T? = this.run {
  try {
    Gson().fromJson<T>(this.toString(), T::class.java)
  }
    catch (e:java.lang.Exception){ e.printStackTrace()
    Log.e("JSONObject to model",  e.message.toString() )

    null }
}


inlinefun<reified T> String.toModel(): T? = this.run {
  try {
    JSONObject(this).toModel<T>()
   }
   catch (e:java.lang.Exception){
    Log.e("String to model",  e.message.toString() )

    null
 }
}

Post a Comment for "Workmanager Data.builder Does Not Support Parcelable"