Впечатления от (попытки) разработки Android-приложений на языке Scala

Пару недель назад наткнулся на занимательный микро-фреймворк под названием Scaloid, который обещал пусть и не превратить разработку приложений под Android в перманентное блаженство, то хотя бы скрасить не слишком радужные будни в той части, которая, по моим наблюдениям, ненавистна почти всем программистам: UI.

Для тех, кто не в курсе: в текущей стадии развития SDK (и, вероятно, так всё будет ещё долгое время) визуальные интерфейсы принято описывать с помощью XML. Всё стандартно: каждый XML-тег отвечает за свой виджет, а параметры отображения описываются с помощью атрибутов. У виджетов есть целочисленный идентификатор, который могут использовать другие виджеты для обеспечения, скажем, позиционирования относительно друг друга. Существует также служебный тег для включения другого XML в текущее место документа.

Идентификаторы виджетам необязательно задавать явно. Можно определить XML с именованными значениями и отправить идентификатор туда, а можно воспользоваться специальным синтаксисом @+id/someId. При сборке утилитой aapt, входящей в Android SDK, такой идентификатор будет сопоставлен с каким-нибудь, но конкретным int value.

Как нетрудно понять, в результате получается большое полотно тегов (а скорее — целая галерея художеств), называющееся layout. Поскольку из XML нет решительно никакой возможности понять, на устройстве с каким экраном запущен конкретный layout, можно создать мильон похожих лейаутов для одного окна с разными модификаторами (размер экрана, dpi и всё-всё-всё) и положить их рядом. Это порождает хаос уже при первичной разработке, а поддержка так и вовсе порой превращается в цирк с NullPointerException, ClassCastException (о причинах которых — чуть ниже) и прочими конями.

Бизнес-логика приложения стоит в стороне от всех layout-ов — и вообще обычно написана на Java, — поэтому для связи двух миров есть layout inflater, осуществляющий парсинг XML и преобразование его в Java-классы. На выходе мы, впрочем, получаем лишь объект класса View — т.е. базовый элемент архитектуры Android UI. И если layout состоит больше, чем из одного виджета (а это происходит решительно всегда, кроме совсем тривиальных случаев), то, очевидно, вся его структура находится внутри View. Получить дочерний элемент по факту можно (удобно) лишь одним способом — вызвав у родителя метод findViewById(Integer), принимающий идентификатор объекта, который необходимо найти, и осуществляющий поиск в глубину иерархии виджетов до первого совпадения.

Метод findViewById лишь ищет что-нибудь с переданным id, не обращая внимания на тип объекта. Возвращается, как нетрудно догадаться, опять же View, который наверняка придётся преобразовать к конкретному типу. А когда ничего не нашлось, будет возвращён null. Как думаете, какой процент разработчиков проверяет возвращённое значение на not-null и на соответствие типов перед преобразованием?

Одни бравые ребята, казалось бы, устали всё это терпеть и создали библиотеку AndroidAnnotations, которая, помимо сокращения размера кода проекта, позволяет инжектить виджеты в неприватные поля классов. На самом деле, всё, что делает библиотека — это генерирует для каждого класса наследника (MainActivity -> MainActivity_), и во внутреннем методе onViewChanged() осуществляет заполнение полей через тот же findViewById(). Впрочем, стоит сказать спасибо за тот факт, что код генерированных классов, в отличие от какого-нибудь Protocol buffers, можно читать без крови из глаз.

AndroidAnnotations скрывает проблемы findViewById от программиста, но не снимает их. Как по мне, идея с XML, возможно, была интересна и прогрессивна лет 10 назад, когда первые прототипы системы Android и были созданы, но с той поры ушло слишком много времени. Одним из сравнительно новых веяний является подход похожий, но с использованием DSL. Яркий пример такого подхода — QML в Qt, для этого же был придуман и, собственно, Scaloid. Но если QML воплощён в жизнь с помощью JavaScript, а поэтому с safety можно попрощаться, то Scaloid — это строгая типизация со всем, что имеется под этим в виду.

Фреймворк, по существу, за идею “хватит фигачить многословные XML, и для кода, и для интерфейса у вас есть выразительность языка Scala”. И с этим сложно поспорить, листинги действительно выглядит приятно и даже привлекательно. Автор даже портировал набор стандартных примеров из поставки SDK (к слову, весьма немаленький) на свой фреймворк, чтобы показать, насколько волосы станут шелковистыми.

Используя этот подход, мы не только избавляемся от дублирования объявлений виджетов, но и имеем возможность сразу создать иерархию виджетов в той конфигурации, которая подходит под конкретные условия. В Scaloid есть удобные средства для определения dpi экрана, его размера и ориентации. А вот стоит ли привязывать Listeners прямо в декларации иерархии, впрочем, уже другой и очень спорный вопрос.

Библиотека заинтересовала меня настолько, что появилась идея попробовать использовать её в новом рабочем проекте. Разумеется, это означало и переход с Java на Scala во всём проекте. Я не мог быть этому не рад, но уже в течение года я время от времени смотрел на растущее понемногу движение Scala&Android, и каждый раз, пробуя свежие версии инструментов разработки, обжигался их сыростью. Сейчас же по какой-то причине появилась уверенность в том, что если даже есть такая Вещь как Scaloid, то с банальной сборкой проблемы уже иссякли. Что ж, чуда не произошло.

Для сборки проектов на Scala можно в теории использовать любые доступные инструменты, такие как Maven или Gradle, но на практике всё иначе. Сборка Android-проекта — само по себе дело самое простое, а в подключении к процессу Scala, которая требует поддержки умного, инкрементального, компилятора и обработки ProGurard-ом даже debug-конфигураций (а ведь хочется ещё и адекватного кэширования результатов обработки), скрыто ещё много подводных камней. Посему де-факто единственным работающим вариантом (без написанием собственных колёс к велосипедам) является конфигурация с sbt. Та поддерживает и инкрементальную, и фоновую пересборку артифактов — в общем, жить с этим можно. Если бы не одно но.

Стоит сказать, что в том новом проекте была не слишком тривиальная (но и не слишком сложная) структура, включающая несколько вложенных друг-в-друга суб-проектов, несколько импортов из aar/apklib — и парочку обычных мавеновских библиотек.

Для sbt понаписано два плагина для Android, один — jberkel/android-plugin, другой — рекомендуемый Scaloid — pfn/android-sdk-plugin. Последний более прост в настройке и вообще активнее развивается. Но оба плагина работают с apklib/aar из рук вон плохо, второй (с первым даже не стал пробовать) плохо понимает даже вложенные alib projects. Нельзя сказать, чтобы код этих плагинов был наполнен магией, но понять, что именно надо исправить, чтобы всё стало работать, за то время, пока я не бросил это дело, мне не удалось. Иначе говоря, затея написать нечто адекватное на Scaloid провалилась с треском.

К самому фреймворку, впрочем, особенных претензий у меня нет. На проектах, не требующих особенных зависимостей, всё работает без нареканий: единственное — пока нет никаких привязок для Fragments и ActionBar, а поэтому всё придётся делать ровно так, как оно было в Java. Я не считаю это особенной проблемой. И если у вас есть время, вы даже можете помочь бравому корейцу Sung-Ho Lee восполнить эти упущения, благо они у него уже есть в ToDo-листе. Провалов в скорости исполнения я не заметил — как и кусков обёртки, где такие провалы могли бы иметь место.

Но, в заключение, большая просьба программистам на Scala. Вам дали хороший инструментарий, где функции и операторы, по сути, одно и то же. Но зачем вы пытаетесь заменить половину именованных функций на набор символьных операторов? Благо, что не египетской “интуитивно-понятной” письменностью. Прочтите, пожалуйста, первое слово из главы Symbolic Method Names вслух несколько раз. Отличия между <--, <---, <<- и <<--- понятны лишь вам — и только. Спасибо.

P.S.: После этого эксперимента вдруг появилась идея попробовать написать подобную библиотеку на языке Kotlin, и это даже не оказалось сколько-нибудь сложным занятием. Попробую написать об этом, лишь доведу и библиотеку, и начатый вместе с ней упомянутый рабочий проект, являющийся по совместительству рабочей площадкой, до конца.