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

 

Опять теория

Для получения записей со стены используется метод wall.get.
Он вернет нам массив записей (JSON), начиная с последней (если нам нужно получать записи с начала списка, то придется использовать в запросе параметр offset), а в поле соunt попадет число всех записей.

Стоит отметить, что пока этот метод построен не очень гибко. Придется делать два запроса к API вместо одного, первый раз — чтобы получить количество записей (count), а второй раз — уже нужные записи, используя первое значение как смещение. Записи в массиве будут располагаться в порядке от ранних к поздним.

У каждого элемента массива записей со стены может быть до десяти приложений (документы, музыка, видео, фото). Вот пример ответа:

response:{
  count:65,
  items:[
    {
      id:92,
      from_id:-50536551,
      owner_id:-50536551,
      date:1469733672,
      marked_as_ads:0,
      post_type:'post',
      text:'Привет ][акер!',
      can_edit:1,
      created_by:3102253,
      can_delete:1,
      can_pin:1,
      attachments:[
        {
          type:'photo',
          photo:{
            id:374032462,
            album_id:-7,
            owner_id:3102253,
            photo_75:'https://pp.vk.me/...ad5/JN_ChKLiMZo.jpg',
            photo_130:'https://pp.vk.me/...ad6/Z-84c1FuwVc.jpg',
            photo_604:'https://pp.vk.me/...ad7/o79JN_hwnWs.jpg',
            width:350,
            height:163,
            text:'Original: https://static38.cmtt.ru/comment-media/77/f0/00/0e8971de9c4360.jpg',
            date:1435299352,
            post_id:1003,
            access_key:'1b927475e9be6cedd1'
          }
        },
        {
          type:'video',
          video:{
            id:171623222,
            owner_id:-50536551,
            title:'Напоминатель паролей',
            duration:90,
            description:'https://play.goog...delphi.wifipassword',
            date:1452846402,
            views:14,
            comments:0,
            photo_130:'https://pp.vk.me/...ideo/s_31105838.jpg',
            photo_320:'https://pp.vk.me/...ideo/l_88896102.jpg',
            photo_800:'https://pp.vk.me/...ideo/x_c377669a.jpg',
            access_key:'71fedc69404803dcb7',
            can_edit:1,
            can_add:1
          }
        },
        {
          type:'audio',
          audio:{
            id:456239307,
            owner_id:2000390511,
            artist:'Научно-технический рэп',
            title:'Тыж программист',
            duration:172,
            date:1469733672,
            url:'https://cs1-32v4....3XvIToniOQ6tamk8E7A',
            lyrics_id:196340205,
            genre_id:3
          }
        },
        {
          type:'doc',
          doc:{
            id:422991754,
            owner_id:3102253,
            title:'Французские переменные.txt',
            size:4017,
            ext:'txt',
            url:'https://vk.com/do...c46f98d38&api=1',
            date:1443852692,
            type:1,
            access_key:'38855b19f953ffaa11'
          }
        }
      ],
      post_source:{
        type:'vk'
      },
      comments:{
        count:0,
        can_post:1
      },
      likes:{
        count:0,
        user_likes:0,
        can_like:1,
        can_publish:1
      },
      reposts:{
        count:0,
        user_reposted:0
      }
    }
  ]
}

Поле type указывает на тип вложения, каждый из них нужно обрабатывать по-своему. Для новостного приложения обычно достаточно текста и картинок. Текст мы получим из поля text, а описание картинки нужно брать из аттача с типом photo, который обладает такими характеристиками, как ширина, высота, ссылки на картинку в разных размерах и собственно описание.

Для обработки видео нам нужно получить его id и сделать дополнительный запрос к методу video.get с параметром videos. Значение для этого параметра должно состоять из owner_id + id (-50536551_171623222). Если владелец видео — группа, то owner_id берется со знаком -. В ответ придет описание видеообъекта.

response:{
  count:1,
  items:[
    {
      id:171623222,
      owner_id:-50536551,
      title:'Напоминатель паролей',
      duration:90,
      description:'https://play.goog...delphi.wifipassword',
      date:1452846402,
      views:14,
      comments:0,
      photo_130:'https://pp.vk.me/...ideo/s_31105838.jpg',
      photo_320:'https://pp.vk.me/...ideo/l_88896102.jpg',
      photo_800:'https://pp.vk.me/...ideo/x_c377669a.jpg',
      files:{
        external:'http://www.youtub...watch?v=vdacaryKf1A'
      },
      player:'https://www.youtu...ryKf1A?__ref=vk.api',
      can_edit:1,
      converting:0,
      can_add:1
    }
  ]
}

В поле files лягут прямые ссылки на видео в разном разрешении или ссылка на внешний источник. В нашем примере это YouTube.
Приложение с типом audio (как и doc) обладает еще и полем url со ссылкой на файл — MP3 или текстовый соответственно.

 

Получение данных

Для клиентского приложения важно получить данные из сети, сохранить их и отобразить пользователю. Способов работы с сетью предостаточно — это могут быть стандартные инструменты или сторонние библиотеки. Для работы с интернетом в SDK есть стандартный класс URLConnection. Ответ нужно разбирать вручную и как-то обрабатывать. Обычно для ответов собирают массив классов, где классы описывают объекты из API.

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

Сейчас в отрасли стандартом де-факто стало использование связки Retrofit + OkHttp. Хорошую статью по ее применению ты найдешь здесь. Удобна эта связка тем, что позволяет работать с сетью как синхронно, так и асинхронно, что очень важно, поскольку Андроид не дает работать с сетью в главном потоке.

 

Разбор ответа

Разбор ответов сервера делается автоматически, через механизм описания модели данных. Проиллюстрирую этот процесс кодом. Здесь мы в главном потоке запрашиваем список новых GIF-картинок для таблички с котами:

CatApiInterface client = ServiceGenerator.createService(CatApiInterface.class);
dataCall = client.getCats("-120909644", "10", String.valueOf(offset));
dataCall.enqueue(new Callback<Data>() {
  @Override
  public void onResponse(Call<Data> call, Response<Data> response) {
    //android.os.Debug.waitForDebugger();
    if (response.isSuccessful()) {
      // request successful (status code 200, 201)
      Data result = response.body();
      LinkedHashSet<CatStore> newStore = new LinkedHashSet<>();
      for (Data.Response cat : result.responses) {
        newStore.add(new CatStore(cat.attachment.doc.url.split("\\?")[0], false,
          Integer.parseInt(cat.text.substring(0, cat.text.indexOf("X"))),
          Integer.parseInt(cat.text.substring(cat.text.indexOf("X") + 1)
        )));
      }
      newStore.addAll(mCatsSet);
      mCatsSet = newStore;
      if (mCatsAdapter != null) {
        mCatsAdapter.applyAndAnimateAdditions(new ArrayList<>(newStore));
      } else {
        mCatsAdapter = new CatsAdapter(mCatsSet, MainActivity.this, Glide.with(MainActivity.this));
        setupRecyclerView(mRecyclerView);
      }
      onItemsLoadComplete();
    } else {
      Toast.makeText(getApplicationContext(), R.string.error_loading, Toast.LENGTH_SHORT).show();
      onItemsLoadComplete();
    }
  }

  @Override
  public void onFailure(Call<Data> call, Throwable t) {
    Toast.makeText(getApplicationContext(), R.string.error_loading, Toast.LENGTH_SHORT).show();
     onItemsLoadComplete();
  }
});
 

API

Работа с методами API описана в интерфейсе:

public interface CatApiInterface {
  @GET("/method/wall.get")
  Call<Data> getCats(@Query("owner_id") String owner_id, @Query("count") String count, @Query("offset") String offset);
  @GET("method/wall.get?owner_id=-120909644&count=1")
  Call<WallData> getCatsNumber();
}

Если API сервиса написан сложно, то приходится делать дополнения к парсингу ответов с помощью класса JsonDeserializer. Иногда бывает трудно понять, что ушло на сервер, а что вернулось, в этой ситуации нам поможет HttpLoggingInterceptor. Вот так выглядит рабочий сервис-генератор:

public class ServiceGenerator {
  public static final String API_BASE_URL = "https://api.vk.com";
  private static Retrofit.Builder builder = new Retrofit.Builder().baseUrl(API_BASE_URL);

  public static <S> S createService(Class<S> serviceClass) {
    // Крутой лог по запросам
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);

    // Свой парсер JSON
    Type responseListType = new TypeToken<List<Data.Response>>() {
    }.getType();
    Gson gson = new GsonBuilder().registerTypeAdapter(responseListType, new JsonDeserializer<List<Data.Response>>() {
      @Override
      public List<Data.Response> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        List<Data.Response> responses = new ArrayList<>();
        if (json.isJsonArray()) {
          for (JsonElement jsonElement : ((JsonArray) json)) {
            if (jsonElement.isJsonObject()) {
              Data.Response resp = context.deserialize(jsonElement, Data.Response.class);
              responses.add(resp);
            }
          }
          return responses;
        }
        return context.deserialize(json, typeOfT);
      }
    }).create();

    // Запуск
    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(logging).build();
    builder.addConverterFactory(GsonConverterFactory.create(gson));
    Retrofit retrofit = builder.client(client).build();
    return retrofit.create(serviceClass);
  }
}

Из описания записи со стены приложения нам нужно получить лишь текст и ссылку на документ. В тексте содержится разрешение картинки, а ссылка нужна для загрузки картинки.

public class Data {
  @SerializedName("response")
  @Expose
  public List<Response> responses;

  public class Response {
    @SerializedName("text")
    @Expose
    public String text;
    @SerializedName("attachment")
    @Expose
    public Attachment attachment;

    public class Attachment {
      @SerializedName("doc")
      @Expose
      public Doc doc;
    }

    public class Doc {
      @SerializedName("url")
      @Expose
      public String url;
    }
  }
}

Загружать картинку в пользовательский интерфейс можно вручную с помощью AsyncTask. А можно немного полениться и воспользоваться библиотекой Fresco (от Facebook) или Glide (рекомендует Гугл). Они же позаботятся о кеше и сэкономят трафик.

Работа с Glide:

Glide.load(URL)
  .diskCacheStrategy(DiskCacheStrategy.SOURCE)
  .into(imageView);

Тут все просто: говорим, откуда грузим картинку, куда вставляем и как работаем с кешем.

 

Хранение данных

Работа с видео, аудио и базами данных — темы обширные и достойные отдельных статей. И по этим направлениям андроид-кодеры имеют неплохой простор для творчества. В качестве БД можно использовать родную SQLite (кстати, создатели Telegram пересобрали ее и получили нехилый прирост производительности), кому-то нравится библиотека Realm — по некоторым тестам она в разы обгоняет SQLite.

Если структура данных не очень сложная, то можно наш массив классов сохранить как текст (gson) в SharedPreferences:

SharedPreferences sp = getPreferences(MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
Gson gson = new Gson();
String json = gson.toJson(mCatsSet);
editor.putString("cats", json);
editor.apply();

И восстанавливать его при каждом запуске:

SharedPreferences sp = getPreferences(MODE_PRIVATE);
Gson gson = new Gson();
String json = sp.getString("cats", null);
Type type = new TypeToken<Set<CatStore>>() {
}.getType();
if (json != null)
  mCatsSet = gson.fromJson(json, type);

Не забудь подключить библиотеку gson в файле gradle:

compile 'com.google.code.gson:gson:2.6.2'
 

Вот и все

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

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