Using Serializable as deep link argument with Android Navigation Component - android

Is it possible to use a custom Serializable object as a fragment argument when using deep links?
I've tried the following - in my navigation graph XML file I've added following lines:
<fragment
android:id="#+id/eventFragment"
android:name="com.myapp.EventFragment"
android:label="EventFragment">
<argument
android:name="event"
app:argType="com.myapp.EventId" />
<deepLink app:uri="myapp://event/{event}" />
</fragment>
Where EventId is a serializable data class:
data class EventId(val value: Long) : Serializable
Then, when I'm trying to run my application with an URL myapp://event/4002, the following exception is thrown:
Caused by: java.lang.UnsupportedOperationException: Serializables don't support default values.
at androidx.navigation.NavType$SerializableType.parseValue(NavType.java:834)
at androidx.navigation.NavType$SerializableType.parseValue(NavType.java:787)
at androidx.navigation.NavType.parseAndPut(NavType.java:96)
at androidx.navigation.NavDeepLink.getMatchingArguments(NavDeepLink.java:99)
at androidx.navigation.NavDestination.matchDeepLink(NavDestination.java:366)
at androidx.navigation.NavGraph.matchDeepLink(NavGraph.java:79)
at androidx.navigation.NavController.handleDeepLink(NavController.java:540)
at androidx.navigation.NavController.onGraphCreated(NavController.java:499)
at androidx.navigation.NavController.setGraph(NavController.java:460)
at androidx.navigation.NavController.setGraph(NavController.java:425)
at androidx.navigation.NavController.setGraph(NavController.java:407)
at androidx.navigation.fragment.NavHostFragment.onCreate(NavHostFragment.java:236)
Replacing type with long (app:argType="long") solves the issue - there is no exception and everything works as expected.
It seems like the navigation library does not know how to convert the raw value from the URL to my EventId class. Is it possible to somehow register an adapter which knows how to convert it? Or maybe there is another solution?

As far as I am aware, you only have one other alternative to what you have done: Parcelable.
I think it will be sufficient in this case, but I am not sure, but even if it doesn't, this will be a good recommendation for any future Android development when you for example need to send data between Activities or fragments.
Especially as I can see you are using Kotlin, which has a helper method for implementing it correctly, see: Parcelize.
With it, your data class would simply look like this:
#Parcelize
data class EventId(val value: Long) : Parcelable
Side note:
Usage of the Serializable interface is discouraged for various reasons. From the book Effective Java written by Joshua Bloch who worked on Java for a long time:
Item 85: Prefer alternatives to Java serialization
If you still have to use Serializable, the book will cover the necessary details.

Related

Parcelables don't support default values. Android navigation deeplink argument

During the implementation of the passing parameter solution, in navigation between modules, I came across a serialization error. Deeplinks, as far as I know, accepts custom argument types, which are Parcelables or Serializable.
Im using newest version of navigation 2.2.0
Error message:
java.lang.UnsupportedOperationException: Parcelables don't support default values.
Am I doing something wrong or this is still under development?
Here is short example:
<fragment
android:id="#+id/sampleFragment"
android:name="com.testapp.app.samples.navigation.SampleFragment"
android:label="SampleFragment">
<argument
android:name="Args"
app:argType="com.testapp.navigation.SampleArgs" />
<deepLink app:uri="app://app/samples/navigation/SampleFragment?Args={Args}"/>
</fragment>
#Parcelize
#Keep data class SampleArgs(
val text: String
) : NavArgs, Parcelable
val x = SampleArgs("TEST")
val uri = Uri.parse("app://app/samples/navigation/SampleFragment?Args=$x")
navController.navigate(uri)
I found something similar here Android Parcelable don't support default values App Crash
It's my first post on stack, so please be gentle :)
EDIT
Here is answer:
https://issuetracker.google.com/issues/148523779
Parcelables currently don't support default values so you need to pass your object as String value. Yes it is a work around.. So instead of passing object itself as Parcelize object we can turn that object into JSON (String) and pass it through navigation and then parse that JSON back to Object at destination. You can use GSON for object to json-string conversion.

Do we really need to avoid constructors with default values for Fragments and Activites in Kotlin?

I am great fan of Kotlin and how it allows us to write better code. One of the best features is interface implementation delegation which looks like this:
class A(val someObject:SomeInterface) : SomeInterface by someObject
someObject has to be singleton (object), has to be created using constructor after keyword by (but then you cannot reference to it, or maybe someone has idea how to do it?) or has to be provided in constructor.
In Android messy and bad world we are discouraged to use constructors in fragments and activites due to configuration changes. But how about this:
class MyFragment(val someObject:SomeInterface = SomeObjectImpl()):Fragment,SomeInterface by someObject
I tried to change configuration and event I allowed system to kill my appliction and still, everything is looking ok, my object is creating again and again with my fragment. Is this valid, or am I missing something?
Happy Kotlin everyone!
This is valid. The reason you're discouraged from overloading fragment constructors is that Android can recreate them, and it will use the default one: MyFragment()
But the way Kotlin implements default parameter values behind the scenes is by creating additional constructors. You can decompile your class and see it contains two constructors now, one receiving someObject, and another empty.
From the JVM perspective the empty constructor would look like this:
public A() {
this(new SomeObjectImpl());
}
Calling it will populate your fragment with new instances of implemented classes.

Implementing both Serializable and Parcelable interfaces from an object in Android - conflict

I have an object that i must save to file for reuse. The class of this object already implements Parcelable for use in intents. My knowledge of saving an object to file says to implement Serializable, but when i do, i get an error in the class that contains this object at the putExtra method of an intent because both Serializable and Parcelable have this method.
Is there a way to avoid this, or just a way that i can save my object state and reload it easily?
I have looked at a few articles and i feel no more informed about how i should be saving my object.
Thanks in advance
I believe that Parcelable and Serializable both reaches the same goal in different ways and with different performances. Given that, if some class in your object hierarchy alread implements the Parcelable interface, you can override its writeToParcel method, call the super for it (so the members of the super classes will be written to the parcel if they were implement that way) and then, you should write your attributes to the parcel, always keeping in mind that the order you use to save them is the order you will use to retrieve them latter (FILO data structure)
EDIT
Just cast your object where it complains and tells about the conflict to the class you want to use as described here: https://stackoverflow.com/a/13880819/2068693
I don't know that you can implement both Serializable and Parcelable together but for convert a class from Serializable to Parcelable you can use this plugin:
Android Parcelable Code generator.
First remove implement Serializable then with ALT + Insert and click on Parcelable you can generate your class.
You have options other than Serializable, but that may meet other requirements such as avoiding library dependencies. You can write objects to file using JSON or XML, which has the advantage of being readable. You may also need to consider versioning - what happens when you have files that need to be read by a class that contains a new field. Persistence brings with it some issues you probably don't have passing Bundles/Intents back and forth.
If you choose Serializable I'd recommend structuring your objects so they can be written to and read from a Bundle. Using a static MyObject.make(Bundle) method and an instance Bundle save() method keeps all the constants and read/write in a single location.

Android - Save Parcelable data into a file

I used to use Serializable objects to save them in filesytem and read them in order to do whatever I want. But Serialization is slow when you have to pass data between activities, so I read than it's recommanded to use Parcelable. Then I did it and yeah it's faster ! But now, I have a little problem. Since Parcelable is optimized for IPC, then they aren't serializable and can't be saved into a file. So I would to know if it's possible to do it.
Also, If I decide to implement both Parcelable and Serializable interface for my class, but only use the Parcelable to pass data between my activities, I would be able to save the class into a file. But I guess than since I use serializable (only to save, not to pass data), this is not a good idea hum ?
I thought too to use Gson library, to serialize data from class, and save the JSON into a file, and reuse Gson to deserialize JSON to get my Parcelable object. Does it seems to be a good idea ? What about performance ?
Thanks to all for your answers!
J.
Just do context.getFilesDir() and use a java.io.ObjectInputStream and java.io.ObjectOutputStream.
Also, with regard to "Parcelable not now serializable". This doesn't entirely make a lot of sense since Parcelable in an interface, not a class you extend.
So,
class MyClass implements Parcelable, Serializable {
}
should work just fine. Once you read and write the object to the file system, the Parcelable interface will still work. It's only an interface.
I have to admit I haven't tried it, but it's what I wrote today and I will be writing the unit test tomorrow.
Hope this helps.
Here's another approach if, as you say there is a conflict between the Parcelable and Serializable interfaces. (Again, that doesn't make sense, but I'll trust you until I finish my unit tests tomorrow)...
Think about this:
Parcel p = Parcel.obtain();
p.writeValue(asset);
p.setDataPosition(0);
byte [] b = p.marshall();
p.recycle();
OOPS, just read the javaDoc for marshall() and it says DO NOT STORE TO DISK. It also says, "Use standard serialization to store to disk" (paraphrase).
So, my first answer should do it for you.
Did you try to use shared preferences? If you need to store key values. Moreover it'll be an XML.

Is it possible to make generic work in AIDL

I have the following AIDL file
package com.mindtherobot.samples.tweetservice;
interface TweetCollectorListener {
void handleTweetsUpdated();
}
I tried to make generic works in AIDL so far. It doesn't work. The following code will flag error.
package com.mindtherobot.samples.tweetservice;
interface TweetCollectorListener<E> {
E handleTweetsUpdated();
}
It seems that generic doesn't work in AIDL. However, that's my guess, as Android Interface Definition Language doesn't talk much on generic.
Just want to confirm, is it true generic doesn't work in AIDL? Is there any workaround?
From The official AIDL docs :
List
All elements in the List must be one of the supported data types
in this list or one of the other AIDL-generated interfaces or
parcelables you've declared. A List may optionally be used as a
"generic" class (for example, List). The actual concrete class
that the other side receives is always an ArrayList, although the
method is generated to use the List interface. Map All elements in the
Map
must be one of the supported data types in this list or one of the
other AIDL-generated interfaces or parcelables you've declared.
Generic maps, (such as those of the form Map are not
supported. The actual concrete class that the other side receives is
always a HashMap, although the method is generated to use the Map
interface.
So, as you can see there is only limited support for generics using Lists, not even Maps, so no a custom parametrized types is not supported.

Categories

Resources