VK.com как бэкенд: подводные камни. Решаем проблемы по заявкам читателей

В прошлых статьях (раз и два) была описана разработка мобильного приложения со всеми любимым VK в роли бесплатного и услужливого сервера. Как это часто случается со всякими хитровыкрученными системами, рано или поздно они обрастают мелкими, но досадными проблемами, затрудняющими их использование. И как только это случается, журнал «Хакер» снова приходит на помощь :).

Версия VK API

При вызове какого-либо метода нужно специально указывать версию API в параметре v — например, последнюю v=5.53. Иначе по умолчанию он ответит тебе версией, созданной еще при Дурове, а именно 3.0. Ее поддержку оставляют из-за очень старых программ, написанных давно, но работающих по сей день. Проблема с версией заключается в разных JSON-ответах сервера. Чтобы не встретиться с новой структурой при работе с методом wall.get , принудительно указывай версию и еще на всякий пожарный — число записей (count). Так API будет вести себя более предсказуемо.

Не забудь про версию API

Хранение записей со стены

Чтобы получить все записи со стены группы и иметь возможность обновлять данные, проще всего каждый раз выкачивать все записи со стены последовательно. Метод wall.get отдает данные с конца стены; используя смещение (offset), можно добраться до конца. Полное количество записей известно из переменной count ответа.

Каждую запись проще хранить в классе. Вот пример:

public class ProductStore implements Serializable, Comparable<ProductStore> {

private String id;
private String category;
private String subcategory;
private String name;
private String price;
private String annotation;
private String storeName;
private String address;
private String photo;
private boolean selected;

private Date startDate, endDate;

Он реализует два интерфейса: Serializable и Comparable. Первый используется для возможности сохранения всей его структуры целиком как строки в SharedPreferences, а второй — для упорядочивания списка по id . Его реализация:

@Override
public int compareTo(ProductStore productStore) {
  return Integer.parseInt(this.getId()) - Integer.parseInt(productStore.getId());
}

Для обновления записей я каждый раз удаляю старую запись из списка (List) и добавляю новую:

mProdList.remove(ps);
mProdList.add(ps);

Так у нас появляется возможность увидеть обновление в записи. Правда, VK дает редактировать новые записи примерно сутки. Позже редактирование отключается и обновление становится недоступно, можно только публиковать заново.

Когда нужно поработать со всем массивом, я запускаю сортировку:

Collections.sort(mProdList);

Загрузка картинок

Для загрузки картинок могут использоваться разные библиотеки: Fresco, Picasso, Universal Image Loader. Я использую Glide, поскольку он поддерживает GIF. Кому-то важна стабильность работы библиотеки, кому-то размер (в том числе количество методов). Я в работе столкнулся со странным поведением Glide при загрузке большого количества картинок в списках: картинки загружались хаотично, а некоторые вообще не отображались.

Поймать ошибку помог слушатель ответов загрузки. Устанавливается он так:

Glide.with(context).load(item.getPhoto())
  .diskCacheStrategy(DiskCacheStrategy.SOURCE)
  .listener(new RequestListener<String, GlideDrawable>() {
    @Override
    public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
      if (BuildConfig.DEBUG)
        Log.e("IMAGE_EXCEPTION", "Exception " + e.toString());
      return false;
    }

    @Override
    public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
      return false;
    }
  })
  .into(viewHolder.imageView);

В лог тут же свалилось

Exception java.net.SocketTimeoutException: timeout

Диагноз ясен: мобильная сеть плохо получает данные с VK с помощью стандартного компонента работы с сетью.

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

compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'

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

Продвинутая работа с Glide

Эта библиотека поддерживает загрузку эскизов изображений. Например, мы можем получить миниатюру GIF-файла и показать ее пользователю, пока загружается основной файл. Нам только нужно знать URL для загрузки картинки.VK API имеет для этого свойство thumb у объекта doc. Добавить в объект записи новое поле, думаю, не составит особого труда, так как он был подробно описан в предыдущих статьях. Теперь нам нужно сделать предзапрос и его результат передать в основную загрузку. Glide сам заменит эскиз на основной документ после его загрузки.

// Запрос на картинку эскиза
DrawableRequestBuilder<String> thumbnailRequest = Glide
  .with(context)
  .load(item.getThumb());
//  Основная загрузка
Glide.with(catsViewHolder.image.getContext())
  .load(item.getThumb())
  .diskCacheStrategy(DiskCacheStrategy.SOURCE)
  .thumbnail(thumbnailRequest)
  .into(catsViewHolder.image);

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

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

Тренироваться лучше на кошках

Получение аватара пользователя

При использовании vk-android-sdk первым делом проверяем, залогинился ли пользователь VKSdk.isLoggedIn. Если да, то нужно запустить метод VKApi.users().get() с дополнительными полями photo_50, photo_100, photo_200. Если он не вернет нам в поле заглушку вроде -http://vk.com/images/camera_a.gif , то мы получим от него аватар в разрешении 50 х 50,100 х 100 или 200 х 200. Зависит это от самого аватара пользователя, раньше можно было загрузить и совсем маленькую картинку.

Вот метод, устанавливающий фото и имя пользователя в ImageView и TextView соответственно:

private void setUserToDrawer(final ImageView userImageView, final TextView userHeader) {
  if (VKSdk.isLoggedIn())
    VKApi.users().get(VKParameters.from(VKApiConst.FIELDS, "photo_50, photo_100, photo_200")).executeWithListener(new VKRequest.VKRequestListener() {
      @Override
      public void onComplete(VKResponse response) {
        VKApiUser user = ((VKList<VKApiUser>) response.parsedModel).get(0);
        // Ищем самое большое изображение
        String photo = null;
        if (!user.photo_200.equals("http://vk.com/images/camera_a.gif"))
          photo = user.photo_200;
        else if (!user.photo_100.equals("http://vk.com/images/camera_b.gif"))
          photo = user.photo_100;
        else
          photo = user.photo_50;

        Glide.with(getApplicationContext())
          .load(photo)
          .diskCacheStrategy(DiskCacheStrategy.SOURCE)
          .into(userImageView);
        userHeader.setText(user.first_name + " " + user.last_name);
      }
    });
}

Еще не конец!

Вместо заключения хочу отметить, что бесплатный VK-бэкенд хорош в использовании, но несет с собой много трудностей. С нашим журналом ты узнаешь о большинстве из них и всегда будешь готов к праведной борьбе за торжество программерской мысли :).

Комментарии (1)

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