WorkManager Data.Builder does not support Parcelable

前端 未结 6 1201
心在旅途
心在旅途 2021-02-20 06:18

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 t

相关标签:
6条回答
  • 2021-02-20 06:51

    First you need to know that the value holder for data is private val mValues which you can not exactly work with to add parcelable to the data but there is a workaround to make the process at least less tedious

    val Data.parcelables by lazy {
       mutableMapOf<String, Parcelable>()
    }
    
    fun Data.putParcelable(key:String, parcelable:Parcelable) : Data{
      parcelables[key] = parcelable
      // to allow for chaining 
      return this
    }
    // in order to get value in the Work class created  Wrok.doWork method
    fun Data.getParcelable(key:String): Parcelable? = parcelables[key]
    
     // build  process
    
     /// you can not add putParcelable to Builder class either since mValues in the builder is also private and Builder.build() return Data(mValues)
     val data = Data.Builder()
                .putBoolean("one",false)
    
                .build()
                .putParcelable("name",parcelable)
    
    val request = OneTimeWorkRequest.Builder().setInputData(data).build()
    
    0 讨论(0)
  • 2021-02-20 06:54

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

    // Serialize a single object.    
    public String serializeToJson(MyClass myClass) {
        Gson gson = new Gson();
        String j = gson.toJson(myClass);
        return j;
    }
    // Deserialize to single object.
    public MyClass deserializeFromJson(String jsonString) {
        Gson gson = new Gson();
        MyClass myClass = gson.fromJson(jsonString, MyClass.class);
        return myClass;
    }
    
    0 讨论(0)
  • 2021-02-20 06:55

    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:

    abstract class SingleParameterWorker<T> : Worker(), WorkManagerDataExtender{
    
        final override fun doWork(): WorkerResult {
            return doWork(inputData.getParameter(getDefaultParameter()))
        }
    
        abstract fun doWork(t: T): WorkerResult
    
        abstract fun getDefaultParameter(): T
    }
    

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

     fun <T> Data.getParameter(defaultValue: T): T {
        return when (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"] as Int,
                    map["field3"] as String,
                    map["field4"] as Long,
                    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.

    0 讨论(0)
  • 2021-02-20 06:59

    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()
        }
        return this
    }
    
    fun Data.Builder.putParcelableList(key: String, list: List<Parcelable>): Data.Builder {
        list.forEachIndexed { i, item ->
            putParcelable("$key$i", item)
        }
        return this
    }
    
    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())
        }
        return this
    }
    
    @Suppress("UNCHECKED_CAST")
    inline fun <reified T : Parcelable> Data.getParcelable(key: String): T? {
        val parcel = Parcel.obtain()
        try {
            val bytes = getByteArray(key) ?: return null
            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()
        }
    }
    
    inline fun <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) ?: return null
        ByteArrayInputStream(bytes).use { bis ->
            ObjectInputStream(bis).use { ois ->
                return ois.readObject() as T
            }
        }
    }
    

    Add proguard rule

    -keepclassmembers class * implements android.os.Parcelable {
      public static final android.os.Parcelable$Creator CREATOR;
    }
    
    0 讨论(0)
  • 2021-02-20 07:04

    In Kotlin, thats how I do it

    Object to Json

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

    To Convert back to model,

    inline fun <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 }
    }
    
    
    inline fun <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
     }
    }
    
    0 讨论(0)
  • 2021-02-20 07:16

    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.
        public static String serializeToJson(Bitmap bmp) {
            Gson gson = new Gson();
            return gson.toJson(bmp);
        }
    
        // Deserialize to single object.
        public static Bitmap deserializeFromJson(String jsonString) {
            Gson gson = new Gson();
            return gson.fromJson(jsonString, Bitmap.class);
        }
    

    Serialize object.

     String bitmapString = 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.

    Data data = getInputData();
    String bitmapString = data.getString(NOTIFICATION_BITMAP);
    Bitmap bitmap = Helper.deserializeFromJson(bitmapString);
    

    Now your bitmap object is ready in Worker class.

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

    0 讨论(0)
提交回复
热议问题