Pull to refresh

Передача List и Map c помощью Intent

Итак, перед нами стоит задача передать из одной activity в другую некий List или Map <key, value>. Казалось бы тривиальная задача? Ан нет.

Естественно, в случае, если у вас достаточно большой набор объектов, лучше использовать Parcelable.
На хабре и статья была на эту тему:
http://habrahabr.ru/post/174015/
А некоторые вообще предлагают отказаться от использования стандартного механизма Java Serialization, и не безосновательно, как вы в дальнейшем увидите:
https://twitter.com/JakeWharton/status/313422727149137920

Впрочем, я сторонник того, что при передаче небольшого количества информации через Intent можно использовать стандартный механизм сериализации, потому что это дешевле с точки зрения разработки, чем писать самостоятельно упаковку через Parcelable.

Давайте напишем немного кода. Пусть у нас есть ActivityA и ActivityB.

В ActivityA напишем:
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.some_layout);
        
        LinkedList <String> stringList = new LinkedList <String>();
                stringList.add("one");
                stringList.add("two");
                stringList.add("three");
        Intent intent = new Intent(this, ActivityB.class);
        //Constants.STRINGS_LIST - любая строковая уникальная константа
        intent.putExtra(Constants.STRINGS_LIST, stringList);
        startActivity(intent);
    }

В ActivityB напишем:
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.some_layout);
        
        LinkedList<String> stringList = (LinkedList<String>) getIntent()
                             .getSerializableExtra(Constants.STRINGS_LIST);
    }

Вроде все правильно? Однако, IDE радостно рапортует o ClassCastException и сообщает, что был получен ArrayList.

Аналогично, если поменять ArrayList, скажем, на TreeMap:
ActivityA:
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.some_layout);
        
        TreeMap<String, String> stringMap = new TreeMap <String, String>();
                stringMap.put("1", "one");
                stringMap.put("2", "two");
                stringMap.put("3", "three");
        Intent intent = new Intent(this, ActivityB.class);
        //Constants.STRINGS_MAP - любая строковая уникальная константа
        intent.putExtra(Constants.STRINGS_MAP, stringMap);
        startActivity(intent);
    }

ActivityB:
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.some_layout);
        
        TreeMap<String, String> stringMap = (TreeMap<String, String> ) getIntent()
                        .getSerializableExtra(Constants.STRINGS_MAP);
    }

В итоге вы получите тот же ClassCastException, и сообщение, что, соответственно, был получен HashMap.

В чем же причина такого поведения?
Extras в Intent - обычный HashMap <key, value>.
Соответственно, Android кладет строковый параметр key и ArrayList, TreeMap, etc как value в Bundle. Дальше он должен сериализовать в байтовый поток, и десериализовать обратно эти объекты. Здесь происходит самое интересное, идет проверка во внутреннем списке типов, в котором есть Integer, Long, List, Map... Как известно, последние 2 пункта - интерфейсы. Таким образом, если мы передаем объект любого типа, реализующего интерфейс List, то на выходе получим LinkedList, если Map - то HashMap соответственно.

Возможные пути решения:
1. Иметь в виду и просто принимать объекты соответствующего типа (не очень удачно).
2. Отказаться от использования Intent. Вместо этого можно использовать, например, сохранение в базу (долго).
3. Использовать кастомный объект с полем типа LinkedList или TreeMap (костыль).
4. Использовать Parcelable (неудобство).

ps.
Возможно есть еще какие-нибудь подобные нестыковки? Например с concurrent-коллекциями? Или альтернативные пути решения проблемы? Пишите в комментариях.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.