Can Nulls be passed to parcel? - android

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)
}

Related

Failed to convert String to long when value is null in firebase ( this is meta)

[Hi all, i've database in firebase, but i had error cannot convert Long to string and string to long when a value is null.This is "meta" variable.
This is model
`public class ModelAnhKhang {
private String id;
private String tenhang;
private String macuon;
private String dvt;
private String ngaynhap;
private String phanloai;
private Long somet;
private Long meta;
private Double tytrong;`
This is Adapter
`#Override
public void onBindViewHolder(#NonNull AnhKhangViewHolder holder, int position) {
final ModelAnhKhang modelAnhKhang = listModelAnhKhang.get(position);
Locale localeUS = new Locale("us","US");
NumberFormat us = NumberFormat.getInstance(localeUS);
holder.txtMaCuon.setText(modelAnhKhang.getMacuon());
holder.txtTenHang.setText(modelAnhKhang.getTenhang());
holder.txtDvt.setText(modelAnhKhang.getDvt());
holder.txtNgayNhap.setText(modelAnhKhang.getNgaynhap());
holder.txtSoMet.setText(String.valueOf(us.format(modelAnhKhang.getSomet())));
holder.txtMetA.setText(String.valueOf(us.format(modelAnhKhang.getMeta())));`
This is Fragment
`private void getHangNhapAnhKhang() {
Query query = reference.child(ANH_KHANG).orderByChild("ngaynhap");
listModelAnhKhang.clear();
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if (snapshot.hasChildren() || snapshot.exists()) {
for (DataSnapshot dss : snapshot.getChildren()) {
ModelAnhKhang modelAnhKhang = dss.getValue(ModelAnhKhang.class);
listModelAnhKhang.add(modelAnhKhang);
}
}
anhKhangAdapter = new AnhKhangAdapter(getContext(), listModelAnhKhang);
rcv_AnhKhang.setAdapter(anhKhangAdapter);
anhKhangAdapter.notifyDataSetChanged();
txtTotal.setText(+anhKhangAdapter.getItemCount()+ " cuộn");
}`
4.This is firebase
dvt: "Kg"
giaban: 23317.99
khoiluong: 7130
macuon: "00340121080180602"
meta: ""
metb: ""
metc: ""
ngaynhap: "2021/09/06"
phanloai: "L2"
somet: 402
tenhang: "Thép dày mạ kẽm Z275 phủ CR3: 1.80mmx1250mm TCT..."
thanhtien: 166257251
tytrong: 17.736318407960198
this is firebase
[1][2]
[1]: https://i.stack.imgur.com/C8jIo.png
[2]: https://i.stack.imgur.com/FD6VB.png
Looks like you are trying to pass "null" to a format
holder.txtMetA.setText(String.valueOf(us.format(modelAnhKhang.getMeta())));
You should handle null values in some way, otherwise java will throw NullPointerExceptions.
Try something like:
if(modelAnhKhang.getMeta() != null){
holder.txtMetA.setText(String.valueOf(us.format(modelAnhKhang.getMeta())));
}else holder.txtMetA.setText("");
This is error when I get data from firebase: field meta = null.
If meta is not null or not equal to "", I will get no error.
Your issue is that you cannot convert ""(blank) or null to long it is not allowed in java. In your line
ModelAnhKhang modelAnhKhang = dss.getValue(ModelAnhKhang.class);
you are doing exactly that, passing incompatible values to a variable of type long. If you are using variable "meta" only to show it on some text (which looks like you do), simply change variable meta to String type and your problem will be solved.
If there is some reason why it needs to be of type long then I'd stil suggest the same solution, only where after you pass value from firebase to meta (which is String now) you later pass it to some long type variable while handling "" and null cases.

Using Float instead of float (Primitive type) when implementing Parcelable

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

How to make LruCache only accept key?

LruCache is a map, but I want to use LruCache to only cache strings, i.e. I want LruCache to work as a HashSet.
How should I define a LruCache class?
LruCache<String, Void> won't work, because when my code called lruCache.put("abc", null), NullPointerException was thrown.
Thanks.
The docs are pretty clear:
This class does not allow null to be used as a key or value. A return value of null from get(K), put(K, V) or remove(K) is unambiguous: the key was not in the cache.
You can define your own string wrapper class to use for values. You can have a special instance of it to represent null strings.
public class StringValue {
public final String string;
public StringValue(String s) {
string = s;
}
public String toString() {
return String.valueOf(string); // in case it's null
}
public String asString() {
return string;
}
public static final StringValue NULL = new StringValue(null);
}
Then you can store a non-null null:
LruCache<String, StringValue> lruCache = new LruCache<String, StringValue>();
lruCache.put("abc", StringValue.NULL);
lruCache.put("def", new StringValue("something"));

Android Parcel class error

I have the complete code copied as is from android.preference.MultiSelectListPreference. I am facing weird compilation errors for the following inner class:
Line #1 is the original code and i have added #Line 2
For Line #2
Type mismatch: cannot convert from void to String[]
and
For uncommented Line #1
Multiple markers at this line
- Type mismatch: cannot convert from void to String[]
- The method readStringArray(String[]) in the type Parcel is not applicable for the arguments ()
private static class SavedState extends BaseSavedState {
Set<String> values;
public SavedState(Parcel source) {
super(source);
values = new HashSet<String>();
//String[] strings = source.readStringArray(); //Line #1
String[] strings = source.readStringArray(values.toArray(new String[0])); //Line #2
final int stringCount = strings.length;
for (int i = 0; i < stringCount; i++) {
values.add(strings[i]);
}
}
public SavedState(Parcelable superState) {
super(superState);
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeStringArray(values.toArray(new String[0]));
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
I am really amused by these compilation errors ! I am actually writing my own multi-select preference but facing the one and only error stated above and have no idea of solving it.
Appreciate any help.
The method readStringArray() has no return value, which is why the compiler is complaining. You need to pass it a string array as a parameter and it fills the string array that you give it.
You call readStringArray() like this:
String[] things = new String[5]; // The array in the parcel is known to have 5 elements
source.readStringArray(things);
This ONLY works if you know exactly how big the string array in the parcel is. If the array is always of a fixed size you can use this. If not, you will need to write the size of the array into the Parcel before you write the array, and then the reader of the Parcel can first read the size of the array, then create a suitably sized string array to receive the data.

How write Java.util.Map into parcel in a smart way?

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*/)
);

Categories

Resources