Шесть лучших библиотек Android-разработчика

Официальный Android SDK содержит в себе достаточный набор классов для создания полноценного приложения. Но будет ли оно лучшим? Сложно с нуля написать одновременно красивое, стабильное и полезное приложение. Иногда ловишь себя на мысли, что метод findViewById ты вызываешь уже в сотый раз и это тебе уже порядком надоело. Благодаря энтузиастам появляются библиотеки, которые существенно упрощают создание сложных конструкций или избавляют от однотипных действий. Сегодня мы разберемся, в чем именно они нам могут помочь.

WARNING


Теоретически создатель библиотеки может допустить малозаметную, но критическую ошибку или даже намеренно вставить деструктивные функции. Будь внимателен!

Внедрение зависимостей

Паттерны в ООП

Паттерны в ООП — это рекомендации, как писать код, чтобы получился гибкий и масштабируемый проект: уменьшать зависимость объектов друг от друга, создавать универсальные классы и так далее. Рекомендую ближе познакомиться с паттернами, прочитав книгу Эрика и Элизабет Фримен «Паттерны проектирования».

Построение многих популярных библиотек основано на использовании ООП-паттерна «Внедрение зависимостей» (Dependecy Injection). Довольно часто требуется создать объект A, для функционирования которого нужен какой-то другой объект B. Возникшая ситуация называется зависимостью, она приводит к дублированию кода и усложняет разработку: как поменять объект B так, чтобы объект A работал?

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

Рис. 1. Подключение библиотек в Android Studio

Dagger 2

Библиотека Dagger 2 предоставляет инструменты для самостоятельной реализации DI-паттерна. Создадим приложение, которое будет загружать из сети какие-либо данные, а затем их обрабатывать.
За загрузку будет отвечать класс ImgLoader:

public class ImgLoader {
  Bitmap image;
  public ImgLoader(String url)
    {loadImage(url);}
  public Bitmap getImage()
  {return image;}
  ...
}

Теперь создадим класс ImgModifier для дальнейшей обработки данных:

public class ImgModifier {
  private Bitmap image;
  @Inject
  public ImgModifier(ImgLoader loader)
    {image=loader.getImage();}
  public String perfomModify()
    {return "modify made";}}

У нас получилась классическая ситуация, когда объекту ImgModifier для работы требуется другой объект. Аннотацией @Inject мы запрашиваем (то есть добавляем в граф новую зависимость) у библиотеки объект ImgLoader. Где же мы его возьмем? Он будет создан в классе с аннотацией @Module:

@Module
public class ImgModule {
  private String urlInt;
  public ImgModule(String urlInt)
    {this.urlInt=urlInt;}

Следующими аннотациями говорим библиотеке, что для графа зависимостей требуется создать (@Provide) по одному экземпляру (@Singletone) объектов ImgLoader и ImgModifier.

@Provides @Singleton
ImgLoader imgLoader()
  {return new ImgLoader(urlInt);}
@Provides @Singleton
ImgModifier provideModifier()
  {return new ImgModifier( new ImgLoader(urlInt));}}

Теперь нужно связать объявленные классы, граф зависимостей и созданные объекты. С помощью следующих аннотаций мы создаем интерфейс, в котором указываем (@Component), что для объекта класса ImgModifier требуется один экземпляр класса ImgModule.

@Singleton
@Component(modules={ImgModule.class})
public interface ImgComponent {ImgModifier myDI();}

Итак, DI-паттерн реализован! Теперь создаем объект ImgComponent. Поскольку зависимости разрешатся уже на этапе компиляции, его конструктор будет называться Dagger[Заданноенамиимя].

ImgComponent component = DaggerImgComponent.builder()
  .imgModule(new ImgModule("URL")).build();

А теперь — результат наших трудов:

ImgModifier imgModifier=component.myDI();

Благодаря библиотеке мы одной строкой создали несколько объектов и разрешили все зависимости. При долгом развитии проекта зависимости между классами будут мешать все больше. Держи в уме рассмотренный паттерн, и рефакторинг кода будет не столь болезненным.

WARNING


Следи за обновлениями: могут появиться как новые возможности, так и заплатки от серьезных багов.

ButterKnife

Возможно, поначалу тебе может показаться, что DI-паттерны — не для тебя :). Но это не так! Ты увидишь, что он может пригодиться и в более обыденных ситуациях. На очереди библиотека ButterKnife, которая поможет в работе с объектами класса View. Теперь нет нужды каждый раз вызывать метод findViewById, достаточно короткого объявления:

@Bind(R.id.imageView) ImageView image;
@Bind(R.id.textView) TextView name;
...

Для последующего использования достаточно вызвать метод ButterKnife.bind(context). Так же легко теперь обработать события onClick, onLongClick и подобные:

@OnClick(R.id.button)
public void submit() {...}

Здесь разрешение зависимостей тоже устроено с помощью рассмотренного паттерна, однако большую часть за нас уже сделала библиотека ButterKnife.

REST-архитектура

REST расшифровывается как передача репрезентативного состояния (Representational State Transfer). Этот термин ввел в обиход Рой Филдинг (Roy Fielding), один из основателей протокола HTTP. В широком смысле REST-приложение представляет собой клиент-серверную архитектуру, в которой один из модулей (сервер) получает запрос от другого (клиент), где уже содержится вся необходимая для ответа информация. Таким образом, серверу нет необходимости хранить у себя какие-либо данные о клиенте. Эту умную фразу можно запомнить для будущих собеседований :), а мы тем временем перейдем к сути. На сегодняшний день, говоря о REST-архитектуре, подразумевают организацию эффективного сетевого обмена. В прошлых статьях мы использовали стандартный Android SDK, но в больших приложениях есть смысл воспользоваться библиотеками.

Retrofit

Пожалуй, самая популярная библиотека для организации RESTful-запросов.

Рис. 2. OpenWeatherMap предоставляет подробный прогноз

С помощью Java-аннотаций возможно легко и прозрачно формировать параметры запросов: заголовки, тело, адрес получателя и прочее. Имеется поддержка синхронных, асинхронных запросов, встроенный разборщик XML и JSON.
Посмотрим, как нам получить прогноз погоды за окном с помощью этой библиотеки. Для этого нам нужно будет осуществить GET-запрос к JSON-странице с сайта openweathermap.org и распарсить массив полученных данных. Первым делом сформируем класс, в который будут помещаться полученные данные. Прогноз погоды имеет следующую структуру:

{
  ...
  "weather":[
    {
      "id":300,
      ...,
      "description":"light intensity drizzle",
      "icon":"09n"
    }
    ...
  ]
}

Нам нужно получить значение элемента description JSON-объекта weather. Для этого создадим свой класс, в который Retrofit самостоятельно поместит значения.

Укажем, что все объекты weather нужно помещать в список weatherData:

public class WeatherModule {
...
@SerializedName("weather")
@Expose
private List<Weather> weatherData;
...

Теперь создадим класс, в который непосредственно и будут заноситься данные:

public class Weather{
  @SerializedName("description")@Expose
  String description;
  ...

Теперь нужно сформировать GET-запрос. Для этого создадим интерфейс WeatherApi, в котором опишем, какой именно запрос мы хотим отправить на сервер:

public interface WeatherApi {
  @GET("/data/2.5/weather")

Сам запрос задается аннотацией. Чтобы получить прогноз, нужно два параметра: город и ID разработчика. Они попадут в сформированный GET-запрос позже.

void fetchData(@Query("q") String city, @Query("appid") String appid,Callback<WeatherModule> response);

И это практически все! Теперь только нужно создать RestAdapter и указать ему интерфейс с параметрами запроса.

RestAdapter restAdapter = new RestAdapter.Builder()
  .setEndpoint("http://api.openweathermap.org").build();
wApi=restAdapter.create(WeatherApi.class);

Теперь можно отправить запрос и обработать полученные данные. Чтобы не блокировать основной поток, сделаем загрузку данных асинхронной. Переопределим два метода, один из которых будет вызван после загрузки данных:

wApi.fetchData("City_ID", "dev_ID", new Callback<WeatherModule>() {
  @Override
  public void success(WeatherModule weatherModule, Response response) {
    Log.v("Current weather:", " " + weatherModule.getDescription());
  }
  @Override
  public void failure(RetrofitError error) {}
});

В несколько строчек нам удалось реализовать создание сетевого соединения, формирование HTTP-запроса, его отправку и обработку ответа сервера. Впечатляет, правда?

INFO


Ищешь альтернативу? Попробуй вбить в Гугл «название_библиотеки vs», наверняка что-то найдется.

Picasso

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

Picasso.with(this)
  .load("URL").rotate(90).resize(50,50)
  .into(image);

Загружать ли каждый раз изображение заново? Как адаптировать его размер? Для всего этого достаточно подключить Picasso, она аккумулирует в себе весь процесс: формирование запроса, кеширование, изменение размера — и все это с минимальными затратами ресурсов ОС.

AndroidViewAnimations

Иногда интерфейс пользователя не удается сделать на сто процентов интуитивным. В такой ситуации всегда есть риск потерять клиента — зачем долго разбираться, если в Google Play полно аналогов? Тогда следует обратить внимание пользователя на какой-нибудь элемент программы с помощью анимации. Библиотека AndroidViewAnimations «оживляет» элементы класса View. Для примера заставим поле TextView мигать в течение 700 мс.

YoYo.with(Techniques.Flash).duration(700).playOn(findViewById(R.id.textView));

Класс Techniques содержит множество вариантов анимации на все случаи жизни, главное — знать меру и вовремя остановиться.

ActiveAndroid

Теперь облегчим себе работу с базами данных. ActiveAndroid реализует так называемое объектно-реляционное отображение — способ, позволяющий связать объектную модель данных (ООП, используемое в Java) с реляционной (базой данных). Каждая таблица тут представляется как класс, а поле — как элементы класса. Таким образом, мы можем работать с содержимым базы данных как с объектами ООП.

Создадим таблицу Artists с полями Name и Bio. В поле Bio будет содержаться информация о названии фильмов (Name) и годе выпуска:

(DateRelease).
  @Table(name = "Artist")
  public class Artist extends Model {
    @Column(name = "Name") public String name;
    @Column(name = "Bio",..)
    public MovieData movieData;
    public Artist()
      {super();}

Поскольку информация о фильме может быть подробной, сохраним ее в объекте MovieData.

@Table(name = "MovieData")
public class MovieData extends Model {
  @Column(name = "name")
  public String name;
  @Column(name="date" )
  public int date;
  public MovieData()
    {super();}
}

Все, наша база данных готова. Теперь заполним ее:

MovieData terminator = new MovieData();
terminator.date=1984;
terminator.name="The Terminator";
terminator.save();

Похожим образом добавим информацию об артисте:

Artist arnold = new Artist();
arnold.name="Arnold Schwarzenegger";
arnold.movieData=terminator;
arnold.save();

Вот так будет выглядеть выборка всех артистов, участвовавших в фильме, если известно только его название. Для начала получим сам объект класса MovieData, о котором идет речь:

public static List<Artist> getArtists(String movieName){
    MovieData movieData = new Select()
      .from(MovieData.class)
      .where("name = ?", movieName)
      .executeSingle();

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

List<Artist> lArtist = new Select()
  .from(Artist.class)
  .where("Bio = ?", movieData.getId())
  .execute();
return lArtist;

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

Рис. 3. Библиотеки постоянно обновляются, следи за новостями

Заключение

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

В рамках одной статьи не раскроешь весь потенциал библиотек, но я уверен, что интерес у тебя появился и с остальными возможностями ты ознакомишься самостоятельно. Если останутся какие-то вопросы, как всегда — готов на них ответить через почту. Удачи!

Похожие материалы