#Parcelize and Superclass, doubling data because of constructor requirement - android

I have a Result parcelable class that is supposed to serve as a container for a key and a parcelable data. This is how I define it:
#Parcelize
open class Result<out T : Parcelable>(val key: String, val data: T?) : Parcelable
The problem is that when defining the child class, in order for #Parcelize to work, I need to add val to the object in the constructor, essentially making the data being written twice to the parcel twice. Here is an example:
#Parcelize
class LessonFinishedResult(private val lessonActivityData: LessonActivityData) :
Result<LessonActivityData>(LessonActivityData.LESSON_KEY, lessonActivityData), Parcelable
I would like to have this:
#Parcelize
class LessonFinishedResult(lessonActivityData: LessonActivityData) :
Result<LessonActivityData>(LessonActivityData.LESSON_KEY, lessonActivityData), Parcelable
But it is not allowed. Is there a smart way I can get around this? And another thing, is there a way I can avoid having to manually use Result<LessonActivityData> and have the type be inferred automatically from the object being passed?
Thanks!

essentially making the data being written twice to the parcel twice
It isn't; the code generated by #Parcelize doesn't care about the superclass at all. It can even not be Parcelizable. Only the primary constructor parameters (in this case val lessonActivityData) are written/read, and when reading it's enough to pass the correct parameters to the superclass constructor.

Related

Proper way of using sealed interface in kotlin

I am totally new in sealed Interface in kotlin. I am trying to state management through sealed in android kotlin. My main goal is when I used the object of sealed i don't want to inherit all child. I am not sure correctly is that sealed interface is right choice for me. All my code may be wrong, please correct if I am wrong Thanks.
sealed interface ConnectionUIState
sealed class BluetoothConnectionUIState {
object Initial : BluetoothConnectionUIState()
data class ScanningDevice(val storedDevice: SnapshotStateList<BluetoothDevice>? = null) : ConnectionUIState
}
I initialise the variable like this
var uiState by mutableStateOf<BluetoothConnectionUIState>(BluetoothConnectionUIState.Initial)
private set
Now I am passing the uiState variable in the function and using when statement
when (uiState) {
BluetoothConnectionUIState.ScanningDevice -> {
xuz()
}
}
why when statement is giving error
'when' expression must be exhaustive, add necessary 'Initial' branch or 'else' branch instead
Also this line is also giving me error BluetoothConnectionUIState.ScanningDevice in when statement.
Error
Classifier 'ScanningDevice' does not have a companion object, and thus must be initialized here
If I am doing wrong here. Can you please elaborate of 2 point of this stack overflow. Thanks
UPDATE
I did some changes
sealed interface ConnectionUIState
sealed class BluetoothConnectionUIState {
object Initial : ConnectionUIState
data class ScanningDevice(val storedDevice: SnapshotStateList<BluetoothDevice>? = null) : BluetoothConnectionUIState()
}
I did success on when statement that it's not complaining about Initial
when (uiState) {
is BluetoothConnectionUIState.ScanningDevice -> {
BluetoothPairContent(viewModel, tryAgainAction, openSettingAction, scanDeviceList)
}
}
That is my goal, but another problem raised that it gives error in uiState initialise time
var uiState by mutableStateOf<BluetoothConnectionUIState>(BluetoothConnectionUIState.Initial)
private set
Error
Type mismatch.
Required:
BluetoothConnectionUIState
Found:
BluetoothConnectionUIState.Initial
Again I am confused. Please guide me on this. Thanks
(I think you worked it out but just in case - you need is in your when to check if something is a class. When comparing to an object you use equality, which can just be written as the value to match)
Working off your update:
// simplified for readability
sealed interface ConnectionUIState
sealed class BluetoothConnectionUIState {
object Initial : ConnectionUIState
data class ScanningDevice : BluetoothConnectionUIState()
}
You've got this object and class nested inside BluetoothConnectionUIState, which means their fully qualified names are things like BluetoothConnectionUIState.Initial. But you don't actually have to nest them, you can do this:
sealed class BluetoothConnectionUIState
object Initial : ConnectionUIState
data class ScanningDevice : BluetoothConnectionUIState()
Now Initial and ScanningDevice aren't nested inside BluetoothConnectionUIState, you just reference them directly. So what's the relationship between them now? Look at the constructors:
// subclass of BluetoothConnectionUIState
data class ScanningDevice : BluetoothConnectionUIState()
// implements the ConnectionUIState interface
object Initial : ConnectionUIState
Once you remove the nesting, you can see that Initial actually has no type relationship with the sealed class at all! It just happened to be located inside it. And that's why you can't put it in your mutableStateOf - it's not a BluetoothConnectionUIState.
It's also why you were getting the must be exhaustive error in your original when block - you only had a branch checking Initial, which isn't part of the sealed class anyway. It works when you check ScanningDevice, because that's the only member of the class - if uiState is a BluetoothConnectionUIState, it must be a ScanningDevice.
How you fix this is up to you - seems like you want those two things to be part of the same sealed class. Maybe you want ConnectionUIState to be the sealed class? Since that's what they both represent. And have BluetoothConnectionUIState be the interface that you can apply selectively to certain members of that sealed class?
sealed interface BluetoothConnectionUIState
sealed class ConnectionUIState {
object Initial : ConnectionUIState()
data class ScanningDevice : ConnectionUIState(), BluetoothConnectionUIState
}
This overview might be a helpful read too!

Android can't serialize Kotlin lambda

we can read lambda function are serialize by default (https://discuss.kotlinlang.org/t/are-closures-serializable/1620),
but I getting error:
java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = com.example.dialogfragment.Arguments)
my class Arguments:
class Arguments(val function: ()-> Unit) : Serializable
What is wrong with my lambda ?
(I got this error when Android need to kill my fragment because low of memory :) )
If you want to serialize or parcel something you need to ensure every field is serializable or parcelable.
In your case you are trying to serialize val function: ()-> Unit which is not serializable. Unfortunately, you can't make function serializable. You should rethink this argument class and pass something different. Probably you can get some parameter which leads to making a decision and then invoking a function in your fragment.

How to parcel PagedList<>?

I've got data class that contains variable of type PagedList?. This class needs to implement Parcelable because I want to save its state inside my Bundle.
How can I parcel objects of this type? #Parcelize doesn't help. How can I write a custom Parceler for PagedList? Or maybe there's another way by some kind of object wrapping?
You just have to give annotation #Parcelize on the class and extend that class to Parcelable and if any property of that class having Another class then that class also should be Parcelable.
And if you getting some error or something wrong then please let me know in comments.

Safe Args: use list of parcelables

I am using the Safe Args plugin with the new Navigation components for my Android project. Now I have an argument that is an array list of parcelables, is there a way to use this with the Safe Args Plugin?
Something like app:argType=ParcelableArray. This should be possible since there are bundle methods like putParcelableArrayList().
Yes, since version 1.0.0-alpha08 you can now pass arrays of parcelable objects like this:
<argument
android:name="users"
app:argType="com.navigation.test.User[]"/>
For passing arrays of primitive types use for e.g. app:argType="integer[]"
Currently i don't think there is a simple way to use list of parcelables with safe args,
But i have found some "hack" to make this work.
For example, i have object 'User' and it parcelable, i am declaring a new parcelable object 'Users' that extending ArrayList().
#Parcelize
data class User(var name: String, val age: Int): Parcelable
#Parcelize
class Users: ArrayList<User>(), Parcelable
Now i can set 'Users' as argument in navigation
<argument
android:name="users"
app:argType="com.navigation.test.Users"/>
And passing array list of parcelables between destinations:
val user=User("Alex", 36)
val users= Users()
users.add(user)
val action=MainFragmentDirections.actionMainFragmentToSecondFragment(users)
NavHostFragment.findNavController(this#MainFragment).navigate(action)
And to retrieve them on other destination:
val users=SecondFragmentArgs.fromBundle(arguments).users
val user=users[0]
txtViewName.text=user.name
txtViewAge.text="${user.age}"
Update:
Support to list of objects coming in alpha8:
https://issuetracker.google.com/issues/111487504
Update 2:
The approach mentioned above will not work in case the activity is recreated, as #Parcelize will not be able to store/restore the list.
The object will be store in the state bundle, however, it will store an empty list of objects.
An improvement to #LaVepe suggestion: as for Android Studio 3.4.2 you can pass Parcelable array with in-built feature of navigation editor by specifying Arguments for chosen destination. Just check 'Array' option after choosing your custom Parcelable class which should not be wrapped in any collection beforehand:
EDIT
Here how it looks in xml:
<argument
android:name="items"
app:argType="com.company.domain.Item[]" />
In your Fragment/Activity code you might strictly pass a typed array of model Parcelable items. If you have non-array collection and write in Kotlin, it may be achieved with (if you have list beforehand) list.toTypedArray().

Does Kotlin's "#Parcelize" work with non data classes?

I have simple data classes. I mean, they are data classes logically, but not data class, because I need inheritance and other constructors. They only have fields (of basic types Int?, String?, or List<String>?, etc), and constructors.
I need to pass them (all of their fields need to be passed) from Activity to Activity, so I need to make them parcellisable (or is there a better way?). I first created them as data class and just added #Parcelize. Even though there was a warning and red line that said "CREATOR" or something, I could ignore them and the app worked as intended.
But, now for the reasons above, I changed them to normal classes, and suddenly there is a compilation error.
Error:java.util.NoSuchElementException: Collection contains no element matching the predicate.
at org.jetbrains.kotlin.android.parcel.ParcelableCodegenExtension.getPropertiesToSerialize(ParcelableCodegenExtension.kt:374)
....too long...
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Error:Execution failed for task ':app:kaptGenerateStubsDebugKotlin'. Internal compiler error. See log for more details
How can I solve this problem? Should I use data class? But I need to parse a JSON object to create them. The classes look like these (not actual classes, but simplified for illustration purposes). Is there a better way than implementing that boring, bulky parcellable code by hand?
#Parcelize
open class Dog():Parcelable
{
var someField1;
var someField2;
constructor(data:JSON):this()
{
//parse data to set the fields.
}
}
#Parcelize
class Doge:Dog
{
var someField3;
var someField4;
constructor():super(); //I do not use it, but for parcellable
constructor(data:JSON):super(data)
{
//parse data to set the fields.
}
}
PS. I had to switch to PaperParcel. It was very similar to Kotlin's, but it did not require a primary constructor. It only required the same thing to be any constructor, so I could just create a secondary constructor with the same argument names as those of fields, and it worked. Although, I wonder why the CREATOR could not be created automatically.
For example,
#PaperParcel
class Doge:Dog
{
var someField3;
var someField4;
//Needed only for PaperParcel
constructor(someField3, someField4)
{
this.someField3 = someField3;
this.someField4 = someField4;
}
companion object
{
#JvmField val CREATOR = PaperParcelDoge.CREATOR
}
//end of PaperParcel stuff.
constructor(data:JSON):super(data)
{
//parse data to set the fields.
}
}
As stated here, your properties should be declared inside your primary constructor.
Parcelable support
Android Extensions plugin now includes an automatic
Parcelable implementation generator. Declare the serialized properties
in a primary constructor and add a #Parcelize annotation, and
writeToParcel()/createFromParcel() methods will be created
automatically:
#Parcelize
class User(val firstName: String, val lastName: String) : Parcelable

Categories

Resources