VK.com is your free backend. Writing our client for the wall of the group vk.com

06.07.2017

In the previous article, we considered several interesting ways to use the bottomless repositories of Pavel Durov’s legacy. In today’s material, I will propose to you to develop this topic and share the skills of developing a client for our favorite social network.

Again the theory

To get records from the wall, the wall.get method is used.
It will return to us an array of records (JSON), starting with the last one (if we need to get records from the beginning of the list, we’ll have to use the offset parameter in the query), and the number of all records will be in the count field.

It is worth noting that while this method is not very flexible. We’ll have to make two requests to the API instead of one, the first time to get the number of records, and the second time to write the necessary records using the first value as an offset. The records in the array will be arranged in order from early to late.

Each element of the array of records from the wall can have up to ten applications (documents, music, video, photos). Here is an example of an answer:

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
      }
    }
  ]
}

The type field indicates the type of attachment, each of which needs to be processed in its own way. For a news application, usually enough text and pictures. Text we get from the text field, and the description of the picture should be taken from the attachment with the photo type, which has such characteristics as width, height, links to the image in different sizes and the actual description.

To process the video, we need to get its id and make an additional request to the video.get method with the video parameter. The value for this parameter must consist of owner_id + id (-50536551_171623222). If the owner of the video is a group, the owner_id is taken with the -. In response, a description of the video object will come.

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
    }
  ]
}

The files field will contain direct links to video in a different resolution or a link to an external source. In our example, this is YouTube.
An application with audio type (like doc) also has a url field with a link to the file – MP3 or text, respectively.

Receiving data

For a client application, it is important to retrieve data from the network, store it and display it to the user. There are plenty of ways to work with the network – these can be standard tools or third-party libraries. To work with the Internet in the SDK there is a standard class URLConnection. The answer must be parsed manually and somehow processed. Usually, an array of classes is collected for the answers, where classes describe objects from the API.

Today, many people prefer to use third-party tools to work with the network. Often, this is due to the need to ensure adequate performance in case of poor communication (yes, in many places of our vast homeland the quality of the mobile Internet leaves much to be desired – telecom operators lease equipment from each other, and at peak hours it is stupidly overloaded).

Now in the industry the de facto standard is the use of the Retrofit + OkHttp bundle. This bundle is convenient because it allows you to work with the network both synchronously and asynchronously, which is very important, because Android does not allow to work with the network in the main stream.

Analysis of the answer

The parsing of server responses is done automatically, through the mechanism for describing the data model. I will illustrate this process with code. Here we in the main thread request a list of new GIF-images for the table with cats:

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

Working with API methods is described in the interface:

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();
}

If the service API is written hard, you have to make additions to the parsing of responses using the JsonDeserializer class. Sometimes it is difficult to understand what went into the server, and what has returned, in this situation will help us HttpLoggingInterceptor. Here is the working service generator:

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) {
    // Cool log on request
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);

    // Own JSON parser
    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();

    // Launching
    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(logging).build();
    builder.addConverterFactory(GsonConverterFactory.create(gson));
    Retrofit retrofit = builder.client(client).build();
    return retrofit.create(serviceClass);
  }
}

From the description of the entry from the application wall, we need to get only text and a link to the document. The text contains the resolution of the picture, and the link is needed to load the picture.

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;
    }
  }
}

You can upload the image to the user interface with the help of AsyncTask. And you can be a bit too lazy and use the library Fresco (from Facebook) or Glide (Google recommends). They will take care of the cache and save traffic.

Working with Glide:

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

Here everything is simple: we say where we load the picture, where we insert it and how we work with the cache.

Data storage

Working with video, audio and databases – the topics are extensive and worthy of individual articles. And in these areas, android coders have a good space for creativity. As a database, you can use native SQLite (by the way, the creators of Telegram recomputed it and got a good performance boost), someone likes the Realm library – in some tests, it overtakes SQLite several times.

If the data structure is not very complex, you can save our class array as text (gson) in 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();

And rebuild it every time you start:

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);

Do not forget to include the gson library in the gradle file:

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

That’s all

With this article, I end our mini-cycle on using VK.com’s capabilities as a free backend for your applications. I hope that the knowledge gained will help you write a real masterpiece and save a little money at the same time. Good luck and see you on our virtual pages!

Нашли ошибку в тексте?

Leave a Reply

Your email address will not be published. Required fields are marked *