Search code examples
androidreact-native

How can i pass a HashMap to a react-native android callback?


When trying to give a callback in a custom native module a java.util.HashMap, I get the following error : java.lang.RuntimeException: Cannot convert argument of type class java.util.HashMap.

I am not very clear on what kind of types can be passed to the callback. Someone suggests on SO using a WritableNativeMap. If so, is there a straightforward way to cast the HashMap ? Or must I iterate through all key/value pairs ?

If not, the docs mention a ReadableMap. Would that be better ?

Many thanks for your help.

Here is some code it case it helps. The method uses the Firebase Android SDK:

public void fetch(String path, final Callback callback) {
    root.child(path).addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot snapshot) {
            if (snapshot.exists()) {
                // snapshot.getValue() is a java.util.HashMap
                callback.invoke(null, snapshot.getValue()); // This triggers the error
            } else {
                callback.invoke(null);
            }
        }
        @Override
        public void onCancelled(FirebaseError firebaseError) {
            callback.invoke(firebaseError.getMessage());
        }
    });
}

Solution

  • For key-value structures, the callback expects a WritableMap, which means whatever data structure you're using needs to be converted to a WritableMap.

    Here is a basic implementation from Firebase's DataSnapshot:

    private <Any> Any castSnapshot(DataSnapshot snapshot) {
        if (snapshot.hasChildren()) {
            WritableMap data = Arguments.createMap();
            for (DataSnapshot child : snapshot.getChildren()) {
                Any castedChild = castSnapshot(child);
                switch (castedChild.getClass().getName()) {
                    case "java.lang.Boolean":
                        data.putBoolean(child.getKey(), (Boolean) castedChild);
                        break;
                    case "java.lang.Integer":
                        data.putInt(child.getKey(), (Integer) castedChild);
                        break;
                    case "java.lang.Double":
                        data.putDouble(child.getKey(), (Double) castedChild);
                        break;
                    case "java.lang.String":
                        data.putString(child.getKey(), (String) castedChild);
                        break;
                    case "com.facebook.react.bridge.WritableNativeMap":
                        data.putMap(child.getKey(), (WritableMap) castedChild);
                        break;
                }
            }
            return (Any) data;
        } else {
            String type = snapshot.getValue().getClass().getName();
            switch (type) {
                case "java.lang.Boolean":
                    return (Any)((Boolean) snapshot.getValue());
                case "java.lang.Long":
                    // TODO check range errors
                    return (Any)((Integer)(((Long) snapshot.getValue()).intValue()));
                case "java.lang.Double":
                    return (Any)((Double) snapshot.getValue());
                case "java.lang.String":
                    return (Any)((String) snapshot.getValue());
                default:
                    return (Any) null;
            }
        }
    }