Requirement: Find out if price is null. Since a primitive float data type cannot be checked for null since its always 0.0, I opted out to use Float instead, as it can be checked for null.
public class QOptions implements Parcelable {
public String text;
public Float price;
}
protected QOptions(Parcel in) {
text = in.readString();
unit_price = in.readFloat();
}
#Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(this.text);
parcel.writeFloat(this.price);
}
However, since the class also implements Parcelable, the writeToParcel crashes with the following exception:
Attempt to invoke virtual method 'float java.lang.Float.floatValue()' on a null object reference
And the exception points to this line:
parcel.writeFloat(this.price);
How can I use the Float data type along with writeToParcel and not cause the exception? Or is there a better way to accomplish my requirement? I just need the price to be null if it's null.
You can handle it in the below manner.
#Override
public void writeToParcel(Parcel dest, int flags) {
if (price == null) {
dest.writeByte((byte) (0x00));
} else {
dest.writeByte((byte) (0x01));
dest.writeFloat(price);
}
}
To read the value of float -
unit_price = in.readByte() == 0x00 ? null : in.readFloat();
Decimal types have a number of special values: NaN, negative and positive infinities. You can use those values to indicate null:
if (price == null) {
parcel.writeFloat(Float.NaN);
} else {
parcel.writeFloat(price);
}
And when reading:
float p = parcel.readFloat();
if (Float.isNaN(p)) {
price = null;
} else {
price = p;
}
NaN means "not a number", so it kind-of fits thematically for serializing things.
Unlike the solution, provided by #Kapil G, this approach does not waste additional 4 bytes for nullability flag (each call to writeByte() actually stores entire int in Parcal for performance reasons).
For parceling Float these two method calls are safe:
dest.writeValue(price);
in.readValue(null);
For parceling any parcelable type you can use this:
SomeType value; // ...
dest.writeValue(value);
in.readValue(SomeType.class.getClassLoader());
List of parcelable types can be found in Parcel docs.
Pros
One line for each read and write.
You don't have to worry about manually differentiating between null and a float when parceling and unparceling. It's done for you internally.
Can express NaN and infinities.
How it works
Here's the relevant part of Parcel source code:
public class Parcel {
private static final int VAL_NULL = -1;
private static final int VAL_FLOAT = 7;
public final void writeValue(Object v) {
if (v == null) {
writeInt(VAL_NULL);
} else if (v instanceof Float) {
writeInt(VAL_FLOAT);
writeFloat((Float) v);
} // ...
}
public final Object readValue(ClassLoader loader) {
int type = readInt();
switch (type) {
case VAL_NULL:
return null;
case VAL_FLOAT:
return readFloat();
// ...
}
}
}
Note that for Float (and other boxed primitives) the loader parameter is unused so you can pass null.
Explore the source here: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/Parcel.java
Is it possible to write null to Parcel when parcelling an object, and get null back again when unparcelling it again?
Let's assume we have the following code:
public class Test implements Parcelable {
private String string = null;
public Test(Parcel dest, int flags) {
source.writeString(string);
}
}
Will I get a NullPointerException when reading this value back from the parcel using Parcel.readString()?
Or will I get a null value out?
Yes, you can pass a null to the Parcel.writeString(String) method.
When you read it out again with Parcel.readString(), you will get a null value out.
For example, assume you have a class with the following fields in it:
public class Test implements Parcelable {
public final int id;
private final String name;
public final String description;
...
You create the Parcelable implementation like this (using Android Studio autoparcelable tool):
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(null); // NOTE the null value here
dest.writeString(description);
}
protected Test(Parcel in) {
id = in.readInt();
name = in.readString();
description = in.readString();
}
When running this code, and passing a Test object as a Parcelable extra in an Intent, 2 points become apparent:
the code runs perfectly without any NullPointerException
the deserialised Test object has a value name == null
You can see similar info in the comments to this related question:
Parcel, ClassLoader and Reading Null values
If you want to write other data types such as Integer, Double, Boolean with possible null values to a parcel, you can use Parcel.writeSerializable().
When reading these values back from parcel, you have to cast the value returned by Parcel.readSerializable() to the correct data type.
Double myDouble = null;
dest.writeSerializable(myDouble); // Write
Double readValue = (Double) dest.readSerializable(); // Read
In my case
(Kotlin)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(if (PapperId == null) -1 else PapperId)
parcel.writeString( if (Nome == null) "" else Nome)
}
I searched for related questions but didn´t find a solution (at least i don´t know if i named it correctly)
So, i have two ArrayLists and i would like to randomize all of them to get a value:
public class ListBox {
public static ArrayList listOne(){
ArrayList<Lists> listOne = new ArrayList<>();
listOne.add(new Item("Text One"));
listOne.add(new Item("Text Two"));
return listOne;
}
public static ArrayList listTwo(){
ArrayList<Lists> listTwo = new ArrayList<>();
listTwo.add(new Item("Text Three"));
listTwo.add(new Item("Text Four"));
return listTwo;
}
}
in other activity:
public void buttonClick(View view){
ArrayList<Lists> algumasListas = ListBox.listOne();
...
}
This is where i shuffle it
public class ListMixer extends ListBox{
public ArrayList<Lists> listas = null;
public ListMixer(ArrayList<Lists> listas ) {
this.listas = listas;
}
protected String mixList(){
Double randomNumber = new Double(Math.random() * listas.size());
int randomNum = randomNumber.intValue();
Lista lista= listas.get(randomNum);
String listaString2 = String.valueOf(lista);
String message = ("This is your list: " + listas);
return message;
}
}
my desired output would be one of the four listItems.
Appreciate the help!
Merge arrays into single one of size N.
Choose a random number in range 0..N-1.
Choose an element by index.
The first bug I'm seeing in your code is that listOne() returns object listTwo when called, which doesn't exist. It probably shouldn't even compile, unless something funky is going on with global scope variables.
The following code should do what you want by merging the two lists into one and then returning a random object from them.
public Object randomFromList(List<Object> listOne, List<Object> listTwo){
List<Object> bigList = new ArrayList<Object>(listOne.size() + listTwo.size());
bigList.addAll(listOne);
bigList.addAll(listTwo);
return bigList.get(new Random().nextInt(bigList.size()));
}
For optimization, if you call this a lot, I would save the Random() object outside of the method to avoid instantiating it every time you make the call.
I have a JSON file that is being parsed with Gson. The problem I have is there are nested arrays in the Json e.g.
"assets":[
{
"Address":"Crator1, The Moon",
"Title":"The Moon",
"AudioFile":null,
"Categories":[
{
"CategoryName":"Restaurants",
"Description":"blah blah",
"ExternalLink":"",
"File":"",
"FileName":"0",
"CategoryID":0,
"ParentCategoryID":786,
"Id":334,
"Image":"",
},
I know it's invalid, it's just for an example. So based on previous questions I have asked and research I believe I should have my code as follows in order to parse the JSON correctly:
public class JsonAssets implements Parcelable{
String Address;
String Title;
String AudioFile;
Categories[] categories;
private class Categories{
String CategoryName;
String Description;
String ExternalLink;
String File;
String FileName;
int CategoryID;
int ParentCategoryID;
int Id;
String Image;
}
}
The class is passed as follows:
JsonReader reader = new JsonReader(new InputStreamReader(is, "UTF-8"));
reader.beginObject();
reader.nextName();
reader.beginArray();
JsonObject obj = null;
while (reader.hasNext()) {
try{
switch(type){
case ASSET_UPDATE:
obj = gson.fromJson(reader, JsonAsset.class);
break;
}
So I read and write the address, audio file as so:
public static final Parcelable.Creator<AssetJson> CREATOR = new Parcelable.Creator<AssetJson>() {
public AssetJson createFromParcel(Parcel in) {
return new AssetJson(in);
}
public AssetJson[] newArray(int size) {
return new AssetJson[size];
}
};
public void writeToParcel(Parcel paramParcel, int paramInt) {
paramParcel.writeString(Address);
paramParcel.writeString(Title);
paramParcel.writeString(AudioFile);
}
private JsonAsset(Parcel in) {
Address = in.readString();
Title = in.readString();
AudioFile = in.readString();
}
The problem I have is I don't know how to read and write Categories[] to the parcel. The fact that it is an object array has me stumped.
This is my first experience with parcelables and I'm trying to take over someone elses code. So if anyone could explain how I would parcel an object array, I'd greatly appreciate it.
Thank you!
Simple, first make your Category class implement Parcelable, implement all the logic for reading and writing back there, then use Parcel class writeParcelableArray method to write an array of parcelables.
Hope this help.
I managed to find the correct solution (for me) so if anyone else is coming up against this, follow this link:
http://www.javacreed.com/gson-deserialiser-example/
It shows how to parse nested array and deserialize.
I have a Generic Map of Strings (Key, Value) and this field is part of a Bean which I need to be parcelable.
So, I could use the Parcel#writeMap Method. The API Doc says:
Please use writeBundle(Bundle) instead. Flattens a Map into the parcel
at the current dataPosition(), growing dataCapacity() if needed. The
Map keys must be String objects. The Map values are written using
writeValue(Object) and must follow the specification there. It is
strongly recommended to use writeBundle(Bundle) instead of this
method, since the Bundle class provides a type-safe API that allows
you to avoid mysterious type errors at the point of marshalling.
So, I could iterate over each Entry in my Map a put it into the Bundle, but I'm still looking for a smarter way doing so. Is there any Method in the Android SDK I'm missing?
At the moment I do it like this:
final Bundle bundle = new Bundle();
final Iterator<Entry<String, String>> iter = links.entrySet().iterator();
while(iter.hasNext())
{
final Entry<String, String> entry =iter.next();
bundle.putString(entry.getKey(), entry.getValue());
}
parcel.writeBundle(bundle);
I ended up doing it a little differently. It follows the pattern you would expect for dealing with Parcelables, so it should be familiar.
public void writeToParcel(Parcel out, int flags){
out.writeInt(map.size());
for(Map.Entry<String,String> entry : map.entrySet()){
out.writeString(entry.getKey());
out.writeString(entry.getValue());
}
}
private MyParcelable(Parcel in){
//initialize your map before
int size = in.readInt();
for(int i = 0; i < size; i++){
String key = in.readString();
String value = in.readString();
map.put(key,value);
}
}
In my application, the order of the keys in the map mattered. I was using a LinkedHashMap to preserve the ordering and doing it this way guaranteed that the keys would appear in the same order after being extracted from the Parcel.
you can try:
bundle.putSerializable(yourSerializableMap);
if your chosen map implements serializable (like HashMap) and then you can use your writeBundle in ease
If both the key and value of the map extend Parcelable, you can have a pretty nifty Generics solution to this:
Code
// For writing to a Parcel
public <K extends Parcelable,V extends Parcelable> void writeParcelableMap(
Parcel parcel, int flags, Map<K, V > map)
{
parcel.writeInt(map.size());
for(Map.Entry<K, V> e : map.entrySet()){
parcel.writeParcelable(e.getKey(), flags);
parcel.writeParcelable(e.getValue(), flags);
}
}
// For reading from a Parcel
public <K extends Parcelable,V extends Parcelable> Map<K,V> readParcelableMap(
Parcel parcel, Class<K> kClass, Class<V> vClass)
{
int size = parcel.readInt();
Map<K, V> map = new HashMap<K, V>(size);
for(int i = 0; i < size; i++){
map.put(kClass.cast(parcel.readParcelable(kClass.getClassLoader())),
vClass.cast(parcel.readParcelable(vClass.getClassLoader())));
}
return map;
}
Usage
// MyClass1 and MyClass2 must extend Parcelable
Map<MyClass1, MyClass2> map;
// Writing to a parcel
writeParcelableMap(parcel, flags, map);
// Reading from a parcel
map = readParcelableMap(parcel, MyClass1.class, MyClass2.class);
Good question. There aren't any methods in the API that I know of other than putSerializable and writeMap. Serialization is not recommended for performance reasons, and writeMap() is also not recommended for somewhat mysterious reasons as you've already pointed out.
I needed to parcel a HashMap today, so I tried my hand at writing some utility methods for parcelling Map to and from a Bundle in the recommended way:
// Usage:
// read map into a HashMap<String,Foo>
links = readMap(parcel, Foo.class);
// another way that lets you use a different Map implementation
links = new SuperDooperMap<String, Foo>;
readMap(links, parcel, Foo.class);
// write map out
writeMap(links, parcel);
////////////////////////////////////////////////////////////////////
// Parcel methods
/**
* Reads a Map from a Parcel that was stored using a String array and a Bundle.
*
* #param in the Parcel to retrieve the map from
* #param type the class used for the value objects in the map, equivalent to V.class before type erasure
* #return a map containing the items retrieved from the parcel
*/
public static <V extends Parcelable> Map<String,V> readMap(Parcel in, Class<? extends V> type) {
Map<String,V> map = new HashMap<String,V>();
if(in != null) {
String[] keys = in.createStringArray();
Bundle bundle = in.readBundle(type.getClassLoader());
for(String key : keys)
map.put(key, type.cast(bundle.getParcelable(key)));
}
return map;
}
/**
* Reads into an existing Map from a Parcel that was stored using a String array and a Bundle.
*
* #param map the Map<String,V> that will receive the items from the parcel
* #param in the Parcel to retrieve the map from
* #param type the class used for the value objects in the map, equivalent to V.class before type erasure
*/
public static <V extends Parcelable> void readMap(Map<String,V> map, Parcel in, Class<V> type) {
if(map != null) {
map.clear();
if(in != null) {
String[] keys = in.createStringArray();
Bundle bundle = in.readBundle(type.getClassLoader());
for(String key : keys)
map.put(key, type.cast(bundle.getParcelable(key)));
}
}
}
/**
* Writes a Map to a Parcel using a String array and a Bundle.
*
* #param map the Map<String,V> to store in the parcel
* #param out the Parcel to store the map in
*/
public static void writeMap(Map<String,? extends Parcelable> map, Parcel out) {
if(map != null && map.size() > 0) {
/*
Set<String> keySet = map.keySet();
Bundle b = new Bundle();
for(String key : keySet)
b.putParcelable(key, map.get(key));
String[] array = keySet.toArray(new String[keySet.size()]);
out.writeStringArray(array);
out.writeBundle(b);
/*/
// alternative using an entrySet, keeping output data format the same
// (if you don't need to preserve the data format, you might prefer to just write the key-value pairs directly to the parcel)
Bundle bundle = new Bundle();
for(Map.Entry<String, ? extends Parcelable> entry : map.entrySet()) {
bundle.putParcelable(entry.getKey(), entry.getValue());
}
final Set<String> keySet = map.keySet();
final String[] array = keySet.toArray(new String[keySet.size()]);
out.writeStringArray(array);
out.writeBundle(bundle);
/**/
}
else {
//String[] array = Collections.<String>emptySet().toArray(new String[0]);
// you can use a static instance of String[0] here instead
out.writeStringArray(new String[0]);
out.writeBundle(Bundle.EMPTY);
}
}
Edit: modified writeMap to use an entrySet while preserving the same data format as in my original answer (shown on the other side of the toggle comment). If you don't need or want to preserve read compatibility, it may be simpler to just store the key-value pairs on each iteration, as in #bcorso and #Anthony Naddeo's answers.
If your map's key is String, you can just use Bundle, as it mentioned in javadocs:
/**
* Please use {#link #writeBundle} instead. Flattens a Map into the parcel
* at the current dataPosition(),
* growing dataCapacity() if needed. The Map keys must be String objects.
* The Map values are written using {#link #writeValue} and must follow
* the specification there.
*
* <p>It is strongly recommended to use {#link #writeBundle} instead of
* this method, since the Bundle class provides a type-safe API that
* allows you to avoid mysterious type errors at the point of marshalling.
*/
public final void writeMap(Map val) {
writeMapInternal((Map<String, Object>) val);
}
So I wrote the following code:
private void writeMapAsBundle(Parcel dest, Map<String, Serializable> map) {
Bundle bundle = new Bundle();
for (Map.Entry<String, Serializable> entry : map.entrySet()) {
bundle.putSerializable(entry.getKey(), entry.getValue());
}
dest.writeBundle(bundle);
}
private void readMapFromBundle(Parcel in, Map<String, Serializable> map, ClassLoader keyClassLoader) {
Bundle bundle = in.readBundle(keyClassLoader);
for (String key : bundle.keySet()) {
map.put(key, bundle.getSerializable(key));
}
}
Accordingly, you can use Parcelable instead of Serializable
Here's mine somewhat simple but working so far for me implementation in Kotlin. It can be modified easily if it doesn't satisfy one needs
But don't forget that K,V must be Parcelable if different than the usual String, Int,... etc
Write
parcel.writeMap(map)
Read
parcel.readMap(map)
The read overlaod
fun<K,V> Parcel.readMap(map: MutableMap<K,V>) : MutableMap<K,V>{
val tempMap = LinkedHashMap<Any?,Any?>()
readMap(tempMap, map.javaClass.classLoader)
tempMap.forEach {
map[it.key as K] = it.value as V
}
/* It populates and returns the map as well
(useful for constructor parameters inits)*/
return map
}
All the solutions mentioned here are valid but no one is universal enough. Often you have maps containing Strings, Integers, Floats etc. values and/or keys. In such a case you can't use <... extends Parcelable> and I don't want to write custom methods for any other key/value combinations. For that case you can use this code:
#FunctionalInterface
public interface ParcelWriter<T> {
void writeToParcel(#NonNull final T value,
#NonNull final Parcel parcel, final int flags);
}
#FunctionalInterface
public interface ParcelReader<T> {
T readFromParcel(#NonNull final Parcel parcel);
}
public static <K, V> void writeParcelableMap(
#NonNull final Map<K, V> map,
#NonNull final Parcel parcel,
final int flags,
#NonNull final ParcelWriter<Map.Entry<K, V>> parcelWriter) {
parcel.writeInt(map.size());
for (final Map.Entry<K, V> e : map.entrySet()) {
parcelWriter.writeToParcel(e, parcel, flags);
}
}
public static <K, V> Map<K, V> readParcelableMap(
#NonNull final Parcel parcel,
#NonNull final ParcelReader<Map.Entry<K, V>> parcelReader) {
int size = parcel.readInt();
final Map<K, V> map = new HashMap<>(size);
for (int i = 0; i < size; i++) {
final Map.Entry<K, V> value = parcelReader.readFromParcel(parcel);
map.put(value.getKey(), value.getValue());
}
return map;
}
It's more verbose but universal. Here is the write usage:
writeParcelableMap(map, dest, flags, (mapEntry, parcel, __) -> {
parcel.write...; //key from mapEntry
parcel.write...; //value from mapEntry
});
and read:
map = readParcelableMap(in, parcel ->
new AbstractMap.SimpleEntry<>(parcel.read... /*key*/, parcel.read... /*value*/)
);