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

WARNING


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

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

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

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

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

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

Рис. 1. Подключение библиотек в Android Studio
Рис. 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 предоставляет подробный прогноз
Рис. 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. Библиотеки постоянно обновляются, следи за новостями
Рис. 3. Библиотеки постоянно обновляются, следи за новостями
 

Заключение

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

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

Оставить мнение