Привет, меня зовут Андрей и я занимаюсь приложениями Тинькофф и Тинькофф Джуниор для платформы Android. Хочу рассказать о том, как мы собираем два похожих приложения из одной кодовой базы.  Тинькофф Джуниор — это мобильное банковское приложение, ориентированное на детей до 14 лет. Оно похоже на обычное приложение для взрослых, только в него добавлены некоторые функции (например, темы оформления), а другие, наоборот, выключены (например, кредитки).  .   На старте проекта мы рассматривали различные варианты его реализации и приняли ряд решений. Сразу же стало очевидно, что два приложения (Тинькофф и Тинькофф Джуниор) будут иметь значительную часть общего кода. Мы не хотели делать форк от старого приложения, а потом копировать исправления ошибок и новый общий функционал. Чтобы работать с двумя приложениями сразу, мы рассматривали три варианта: Gradle Flavors, Git Submodules, Gradle Modules. Многие наши разработчики уже пробовали использовать Flavors, плюс мы могли применить многомерную сборку (multi-dimensional flavors) для использования с уже существующими flavors.
Однако Flavors имеют один фатальный недостаток. Android Studio считает кодом только код активного флейвора — то есть то, что лежит в папке main и в папке флейвора. Остальной код считается текстом наравне с комментариями. Это накладывает ограничения на некоторые инструменты студии: поиск использования кода, рефакторинг и другие. Еще один вариант реализации нашей идеи — использовать сабмодули гита: вынести общий код в отдельный репозиторий и подключать его как сабмодуль к двум репозиториям с кодом конкретного приложения.  Этот подход увеличивает сложность работы с исходным кодом проекта. Также разработчикам все равно пришлось бы работать со всеми тремя репозиториями, чтобы вносить правки при изменении API общего модуля. Последний вариант — перейти на многомодульную архитектуру. Этот подход лишен недостатков, которые есть у двух других. Однако переход на многомодульную архитектуру требует временных затрат на рефакторинг.  На момент начала работы над Тинькофф Джуниор у нас было два модуля: маленький модуль API, описывающий работу с сервером, и большой монолитный модуль application, в котором была сосредоточена основная часть кода проекта. 
В итоге мы хотели получить два модуля приложений: adult и junior и некий общий core-модуль. Мы выделили два варианта: У нас было время в запасе, и мы решили начать разработку по первому варианту (модуль common) с условием перейти к быстрому варианту, когда у нас закончится время на рефакторинг.
В итоге так и случилось: мы перенесли часть проекта в модуль common, а потом оставшийся модуль application превратили в библиотеку. В результате сейчас мы получили такую структуру проекта:  У нас есть модули с фичами, что позволяет нам разграничивать «взрослый», общий или «детский» код. Однако модуль application всё еще достаточно большой, и сейчас там хранится около половины проекта. В документации есть простая инструкция по превращению приложения в библиотеку. Она содержит четыре простых пункта и, казалось бы, никаких трудностей быть не должно: Однако конвертация заняла несколько дней и итоговый дифф получился таким: Прежде всего в библиотеках идентификаторы ресурсов — это не константы. В библиотеках, как и в приложениях, генерируется файл R.java со списком идентификаторов ресурсов. И в библиотеках значения идентификаторов не являются константными. Джава не позволяет делать свитч по неконстантным значениям, и все свитчи нужно заменить на if-else. Далее мы столкнулись с коллизией пакетов.
Предположим, у вас есть библиотека, у которой package = com.example, и от этой библиотеки зависит приложение с package = com.example.app. Тогда в библиотеке будет сгенерирован класс com.example.R, а в приложении, соответственно, com.example.app.R. Теперь создадим в приложении активити com.example.MainActivity, в которой попробуем обратиться к R-классу. Без явного импорта будет использован R-класс библиотеки, в котором не указаны ресурсы приложения, а только ресурсы библиотеки. Однако Android Studio не подсветит ошибку и при попытке перейти из кода к ресурсу всё будет окей. В качестве фреймворка для инъекции зависимостей мы используем Dagger.
В каждом модуле, содержащем активити, фрагменты и сервисы, у нас есть обычные интерфейсы, в которых описаны inject-методы для этих сущностей. В модулях приложений (adult и junor) интерфейсы-компоненты даггера наследуются от этих интерфейсов. В модулях мы приводим компоненты к необходимым для данного модуля интерфейсам. Разработку нашего проекта значительно упрощает использование мультибиндингов.
В одном из общих модулей мы определяем интерфейс. В каждом модуле приложения (adult, junior) описываем реализацию этого интерфейса. С помощью аннотации @Binds указываем даггеру, что всякий раз вместо интерфейса необходимо инжектить его конкретную реализацию для детского или взрослого приложения. Также мы нередко собираем коллекцию реализаций интерфейса (Set или Map), при этом такие реализации описаны в разных модулях приложения. Для разных целей мы собираем несколько вариантов приложения. Флейворы, описанные в базовом модуле, должны быть описаны и в зависимых модулях. Также для корректной работы Android Studio необходимо, чтобы во всех модулях проекта были выбраны совместимые варианты сборки. За короткий срок мы реализовали новое приложение. Теперь мы отгружаем новый функционал в двух приложениях, написав его один раз.  При этом мы потратили некоторое время на рефакторинг, попутно уменьшив технический долг, и перешли на многомодульную архитектуру. По пути мы столкнулись с ограничениями со стороны Android SDK и Android Studio, с которыми успешно справились. Android developer