DroidParts — библиотека для Android 8-in-1

Этой статьёй я открываю цикл, посвящённый разработке Android-приложений. Но не типичных для Google Play, написанных, очевидно, задней левой mНогой, а приложений корректных и элегантных. В этом деле нам поможет DroidParts — швейцарский нож Android-библиотек с 8 лезвиями:

  1. Dependency Injection: инициализация системных и собственных зависимостей;
  2. Object-Relational Mapping для SQLite: CRUD-операции «из коробки»;
  3. Поддержка Fragments, ActionBarSherlock;
  4. Простая (де-)сериализация JSON;
  5. AsyncTasks, IntentService с обработкой Exсeptions;
  6. RESTful HTTP client с поддержкой JSON;
  7. L.og без указания тега, конфигурируемый через AndroidManifest;
  8. Утилиты типа асинхронного http-загрузчика с кешированием, View- и Intent-помощников.


Буквально час назад* увидела свет первая версия библиотеки, но сначала…

Долгое время я был приверженцем олдскульного подхода с кодом в стиле:

private Button btn;

onCreate(Bundle b) {
	super.onCreate(b);
	btn = (Button)findViewById(R.id.btn);
}

До тех пор, пока не поучаствовал в проекте, использовавшем RoboGuice. И после:

@­InjectView
private Button btn;

добровольного возврата назад не было.

С другой стороны, RoboGuice нервировал размером и всё никак не зарелизенной версией 2.0 с поддержкой Fragments. А ещё, для ORM нужна была сторонняя библиотека. И Gson для упрощения работы с JSON. Неправильно это, тянуть зависимостей на несколько размеров приложения.

В общем, следуя принципу "if you want something done right, do it yourself", я приступил just for fun.

То, что из этого получилось, доступно в версии 0.5 по адресу github.com/yanchenko/droidparts и состоит из:
  • base — DI, ORM, Activities и прочие ключевые компоненты;
  • extra — опциональные RESTful клиент и различные утилиты;
  • support-actionbarsherlock — поддержка Fragments, ActionBar и табов в связке с умолчательным ActionBarSherlock;
  • sample — «образцовое» приложение.
С рассмотрения последнего мы и приступим.

Для начала делаем git clone github.com/yanchenko/droidparts.git и импортируем Eclipse-проекты из base, extra, sample.
Первые два из них — библиотеки (Android library project).
В project.properties последнего, который полноценное приложение, обратите внимание на строку proguard.config=../proguard.cfg, указывающую на видоизменённый конфиг обфускатора, используемый при экспорте в .apk.

Хотя DroidParts содержит кастомное org.droidparts.Application, наследовать от него имеет смысл только в случае, если инъекция нужна в самом Application. Для простого приложения такой необходимости нет. Зато нужна инъекция собственных зависимостей.
В AndroidManifest.xml строка:

<meta-data android:name="droidparts_dependency_provider" android:value=".DependencyProvider" />

деклариует класс, наследующий org.droidparts.inject.AbstractDependencyProvider и являющийся фабрикой (factory) необходимых нам объектов.

Схема простая: каждый метод DependencyProvider'a возвращает instance одного из классов, доступных к инъекции. Поддерживаются два типа методов: вызываемые без параметров и с единственным параметром Context. В случае, когда инъекция производится в Activity, передаваемый Context будет именно ей, что важно для зависимостей, связанных с UI.

Примеры в коде, в т.ч. каким образом реализуются одиночки (singletons, ахаха, обожаю термины в переводе), можно посмотреть непосредственно в org.droidparts.sample.DependencyProvider.
Для инъекции системных ресурсов и сервисов дополнительные методы, естественно, не нужны.

Сама инъекция производится для полей, аннотированных @­Inject... при вызове одного из методов Injector.get().inject(...). Ручной вызов можно пропускать, наследуя от Activity, Service и т.д. из org.droidparts.
В приложении org.droidparts.sample.activity.EntryListActivity — пример такого подхода.
Обратите внимание на:

	@Override
	public void onPreInject() {
		setContentView(R.layout.activity_entry_list);
	}

Все вызовы в onCreate(...) произойдут после того, как Injector завершит работу. В данном случае это привело бы к ненайденным Views.

На этой сумбурной недосказанности я и завершаю вводную статью. Примеры DI, ORM и JSON-сериализации, достаточные, чтобы начать использовать библиотеку, есть в приложении. Подробное рассмотрение и best practices — тема для дальнейших статей.
В ближайшее время я расскажу, чем можно заменить собственные конструкторы для Activity (hint: в приложении этот паттерн есть).

На вопросы отвечу здесь или там: stackoverflow.com/questions/tagged/droidparts.

Спасибо, и пожалуйста, пишите меньше кода! (:

* по состоянию на вчерашний вечер.
Поделиться публикацией

Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 45

    0
    Хм, под Android модно писать IoC фреймворки :)
      +1
      Надо же чем-то занимать многоядерные процессоры! :)
      +6
      У вас проблемы с классом org.droidparts.model.Entity:
      1. Он должен быть абстрактным
      2. HashCode не должен включать имя класса
      3. Equals должен делать instance of вместо o.getClass() == this.getClass()
        +7
        ModelUtils просто так хавает Exception
        try {
            //...
        } catch (Exception e) {
            L.d(e);
        }
        
          +6
          Называть имена классов своих классов также как и имена стандартной библиотеки — плохой тон:
          class ListActivity extends android.app.ListActivity
          
            +6
            Вы уверены, что хранить Context полем в AsyncTask — хорошая идея?
            protected final Context ctx;
            

            Что произойдёт если Activity закончит своё существование до завершения задачи?

            Это я всё к тому, что перед тем как выкладывать библиотеку в открытый доступ (читать — делать публичный API), нужно сто раз подумать.

            А в чём собственно проблема — натянуть зависимостей? Ну будет размер приложения ~ несколько МБ, это разве критично?
            Зато — оттестированные, широко используемые библиотеки, которые скорее всего обновляться будут чаще…
              +5
              Классическая ошибка создания синглетона;
              public static Injector get() {
                  if (injector == null) {
                      synchronized (Injector.class) {
                          if (injector == null) {
                              injector = new Injector(new InjectorDelegate());
                          }
                      }
                  }
                  return injector;
              }
              


              Почитайте, например, это

              Понятно, что в однопотоковой среде проблем не будет, но если вы решили сделать синглетон, то почему бы не сделать его правильно? =)

              Я за простоту и написал бы:
              private static final Injector injector = new Injector();
              
                +1
                Спасибо за code review, учту.
        0
        > Неправильно это, тянуть зависимостей на несколько размеров приложения.

        Обфускатор на ура отсекает неиспользуемый код, в результате чего размер приложения становится минимальным, сколько бы зависимостей ни тянул проект.
          +1
          Правильно, отрежет неиспользуемый. Используемого тоже много.
          Уменьшение размера — это скорее эстетический бонус.
            0
            ответьте пожалуйста на мой вопрос в конце комментариев
          0
          Можете подсказать, где именно реализован асинхронный http-загрузчик с кэшированием? Недавно проводил небольшое исследование на эту тему, на удивление — готовых решений не нашёл. Или самоделки на IntentService или AsyncTask, или допиливание Loader-ов, которые сами гугловцы считают сырыми.
            0
            Пожалуйста: org.droidparts.util.ImageAttacher.
            В первую очередь это утилита для загрузки ListView-картинок.
              +1
              Откуда информация, что «гугловцы считают сырыми»?
              0
              Много раз смотрел в сторону анотаций, но все же жду FP на Android. lazy val button = findView(R.id.button) куда лучше смотриться :) Scala не подошла, из-за скорости сборки, и видимо не улучшиться в скором, Kotlin пока что подбирается все ближе и ближе. Но идея оптимизации кода и убрать много лишнего достойна похвал. Уверен библиотека найдет применение.
                0
                Спасибо. Я каждый раз, возвращаясь с Objective-C на Java, думаю, что имеющегося синтаксиса мне вполне достаточно для счастья. :)
                0
                автор, расскажите плз, почему в AndroidHttpClientWrapper вы используете рефлексию для создания AndroidHttpClient?
                  0
                  Т.к. AndroidHttpClient появился в API 8, а минимальная для библиотеки — 7.
                    0
                    так так так, а если мы на 7-м апи запустим, оно же просто IllegalArgumentException выкинет выше. это ок? может быть, стоило в таком случае DefaultHttpClient создать? и ещё один вопрос — зачем минимальная версия 7?
                      0
                      и вообще я что-то не понимаю, где у вас этот враппер используется
                        +1
                        API 7 — необходимый минимум. Т.к. 5% устройств с Android 2.1 ещё остаётся.
                        Единственный вызов AndroidHttpClientWrapper закомментирован в RESTClient с //TODO, так что по факту DefaultHttpClient и используется. Исправлю, т.к. судя по описанию, AndroidHttpClient стоит использовать.
                          +1
                          Насколько я понял из их блога они забили болт и на Default и на Android httpclient и теперь рекомендуют всюду пихать их модный urlconnection: android-developers.blogspot.com/2011/09/androids-http-clients.html
                          В котором есть пара багов на старых версиях, но по ссылке выше даны рекомендации как их зарефликсить для старых версий. Тем не менее лично мне как то ближе и гибчее и документированней Default клиент.

                          За либу — спасибо, по крайней мере буду подглядывать как люди пишут, хотя использовать и не буду.
                          У меня немного другой джентельменский набор утилит:
                          — свой акшен бар (на базе не помню чьего) либо ставлю шерлок + компатибилити лайбрари если размер не имеет значаения
                          — враперы над асинктасками на базе активити и фрагментсактивити (обе либы были на хабре)
                          — выдранныйе из greendroid набор утилит для работы с изображениями типа скалить, маски + асинкимаджвью
                          — виджеты скроллинг техтвью и акшенбар
                          — рестфул http слиент на базе intenyservice
                          Инжектами не пользуюсь, а с бд пока не сталкивался

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

                          Минус в том что все это как не оформлено в единном стиле чтоли, нет фабрик и всяких абстрактных враперов, просто набор кода. Но для меня плюс более критичен
                            0
                            Спасибо за ссылку, завёл issue.
                            И за опыт тоже, поищу здесь информацию о
                            враперы над асинктасками на базе активити и фрагментсактивити (обе либы были на хабре)

                            Кстати, про работу в фоне.
                            Для разовых ad hoc задач я использую простые враперы над AsyncTask, для повторяющихся — SimpleIntentService. В обоих есть поддержка Exceptions для выполняемого в фоне (идея из RoboGuice) и Listener для сообщения о результате. С Loaderами пока не сложилось.
                          0
                          Предпочитаю использовать google http client, а не самописные решения. Библиотека использует ту или иную реализацию http клиента в зависимости от версии платформы и предоставляет удобный апи для разбора ответа. Ну и множество других плюшек, разумеется.
                            +3
                            Как показывает практика с тем же urlconnection — как раз решения от гугла — самописные поделки с багами в самых неожиданных местах. Апачевскому коду лично у меня больше доверия.
                            Вообще конкретно заебало уже постоянно латать гуглОвские недоработки в андроиде. То они табактивити депрекейтят, то в асинктаске связь с активити теряют то память в урлконнектион течет то сука неожиданно вместо мультитрединга в сингтрединг тот же асиктаск переключают на ICS. Да что уж там — за хрен знает сколько лет так и не удосужились нормальных виджетов нарисовать, чтоб я уже не говорю о уровне эпл, хотя бы на уровне htc выглядели кнопки из коробки. Чтобы сука не стыдно было в приложения дефолтные контролы вставлять. Чтобы тени были, скругления, а не как в 90-х годах. Пишут они на редкость небрежно. Непродуманно. Без перфекционизма. Хотя вроде есть и нормальные библиотеки у них, тот же протобаф или там gson, но к андроиду, в целом, это к сожалению не относится.
                            Отсюда и появляются вские враперы типа выше или тот же шерлок, гриндроид и иже с ними.
                              0
                              Шелрлок нужен для обратной совместимости.
                                0
                                Для обратной совместимости не обязательно ставить шерлок, можно поставить compatibility-library, шерлок основан на ней
                                developer.android.com/sdk/compatibility-library.html
                                  0
                                  Шерлок — имплементация ActionBar для пре 11-го андроида. Сompatibility-library не содержит ActiobBar. Рекомендация от Google — использовать Action Bar Compatibility
                                    0
                                    не видел раньше этой либы, спасибо
                                0
                                Да, @Deprecated TabActivity без адекватной Fragment-based замены — это жесть.
                                Хочется надеяться, что с 4.0 у них сформировалось долгосрочное видение как по пользовательскому интерфейсу, так и по API.
                                  0
                                  Ну есть запутанный туториал от Великих Программистов из Гугл:
                                  developer.android.com/resources/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.html

                                  Но я с ним не разобрался. Я так понял в фрагментактивити на онкреэйт стоит как раз та самая заглушка которая должна подымать фрагмент из памяти при открытии таба. Однако в таб получается добавить только класс фрагмента, а фрагментактивити на фрагмент не кастится и весь кайф пропадает, получается что раньше при клике на табе все заново онкреэйтилось, что сейчас, на новых модных фрагментах — все онкреэйтится при каждом клике(
                                    0
                                    О да, я тоже начинал разбираться с этим чудом инженерной мысли и быстро понял, что такое я в проектах использовать не хочу.
                                    В итоге написал TabbedFragmentActivity. Вкратце: каждый таб — это набор фрагментов, которые при переходе между табами скрываются/отображаются.
                                      0
                                      ну а чем ваше решение отличается/лучше стандартного API для табов в ActionBare?
                                        0
                                        Оно дополняет. Из доков:
                                        To switch between fragments using the tabs, you must perform a fragment transaction each time a tab is selected.

                                        Этим TabbedFragmentActivity и занимается. По аналогии с прежней TabActivity.
                                          0
                                          я вас понял. но по сути ваше решение точно так же добавляет табы в ActionBar, как и пример из доков
                                      0
                                      ну этот костылек для того и написан, чтоб совать фрагменты в tabhost. TabActivity а с ним и ActivityGroup потому и сделали deprecated, чтобы люди не совали Activity/FragmentActivity в табы. Теперь вместо них лучше юзать фрагменты.
                                      По умолчанию относительно удобный режим табов есть для ActionBara. Он нормально выглядит и подстраивается под разные конфигурации UI. если вам нужен именно tabhost, то по вашей ссылке решение для tabhosta.
                                  0
                                  Интересное самописное решение от Google. :)
                        0
                        А что в качестве ORM используется? что-то самописное или уже готовый ORM взяли?
                          0
                          Своё. Идеологически похоже на OrmLite (а не ActiveRecord например), только проще и с учётом Android-специфики типа столбца "_id". Пока можно посмотреть в sample приложении, в дальнейшем опишу более подробно.
                            0
                            понятно а на сколько уже юзабельно? в продакшине использовать можно?
                              0
                              Юзабельно для примитивов, Strings, (UUID, Enum, Drawable есть, но не тестировал) и полей с инстансами наследников от Entity (которые становятся foreign keys). Сейчас делаю сохранение массивов/коллекций.
                              Практически всё, что есть в библиотеке, уже используется в продакшине.
                          +2
                          «Но не типичных для Google Play, написанных, очевидно, задней левой mНогой, а приложений корректных и элегантных.» т. е., по-вашему, приложение, не использующее плюшек типа ioc, orm, и прочего мусора по-умолчанию считается написанным некорректно, неэлегантно, и вообще г*вно?
                            0
                            Не обязательно.
                              +1
                              А звучит именно так. «Написанные стандартными SDK средствами приложения — говно, а разработчики, старающиеся выжать последнюю каплю производительности, скажем, используя курсор, bulkInsert с инсерт хелпером, компилирующим statement единожды для вставки, скажем, 10000 записей за 200мс на самом деле просто лохи, т. к. не используют навороченный ORM.»

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое