VK.COM é um seu backend gratúito. Desenvolvimento de nosso cliente para muro do grupo vk.com

By | 03.08.2017

No artigo anterior consideramos vários jeitos interessantes de usar repositórios sem base de legacy do Pavel Durov. No material de hoje vou propor desenvolver este tópico e compartilhar as habilidades de desenvolvimento de um cliente para nossa rede social favorita.

Novamente a teoria

O método wall.get foi utilizado para obter registros do muro.

O método vai retornar para nós uma série de registros (JSON) começando com o último (se precisarmos obter registros do início da lista, temos que usar o parâmetro de deslocamento na consulta) e o número de todos os registros estarão no campo de contagem.

Deve anotar que esse método não é muito flexível. Temos que fazer dois pedidos para a API em vez de um, sendo o primeiro é para obter o número de registros e segundo é para escrever os registros necessários usando o primeiro valor como um deslocamento. Os registros em matriz serão organizados de antigos para mais recentes.

Cada elemento da matriz de registros do muro pode ter até dez aplicativos (documentos, música, vídeo, fotos). Aqui está um exemplo de uma resposta:

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

O campo de tipo indica o tipo de anexo cada um dos quais deve ser processado de maneira diferente. Para um aplicativo de notícias, geralmente texto e imagens é suficiente. Texto recebemos do campo de texto e a descrição da imagem deve ser tirada do anexo com o tipo de foto. A foto tem características como largura, altura, links para a imagem em tamanhos diferentes e descrição.

Para processar o vídeo precisamos obter sua ID e fazer uma solicitação adicional para o método video.get com parâmetro de vídeo. O valor para este parâmetro deve consistir em owner_id + id (-50536551_171623222). Se o proprietário do vídeo é um grupo, o owner_id é tomado com o -. Em resposta vamos receber a descrição do objeto de vídeo.

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

O campo de arquivos vai contar links diretos para o vídeo em uma resolução diferente ou um link para uma fonte externa. No nosso exemplo isso é o YouTube.

Um aplicativo com tipo de áudio (como doc) também possui um campo de url com um link para o arquivo (MP3 ou texto respetivamente).

Recebendo dados

Para um aplicativo cliente, é importante recuperar dados da rede, armazená-los e exibi-los para o usuário. Há muitas maneiras de trabalhar com rede, estas podem ser ferramentas padrão ou bibliotecas de terceiros. Para trabalhar com a Internet no SDK existe uma classe normal URLConnection. A resposta deve ser analisada manualmente e de alguma forma processada. Normalmente uma série de classes é coletada para as respostas onde classes descrevem objetos do API.

Hoje muitas pessoas preferem usar ferramentas de terceiros para trabalhar com a rede.

Maioria das vezes isso relacionado com necessidade de garantir um desempenho adequado em caso de conexão ruim (sim, nos muitos lugares da nossa grande pátria a qualidade da Internet móvel deixa muito a desejar, as operadoras de telecomunicação alugam equipamento uns dos outros, e nas horas pico tudo está sobrecarregado).

Agora o padrão da indústria é de fato o uso do pacote Retrofit + OkHttp. Este pacote é conveniente porque permite que você trabalhe com rede de forma síncrona e assíncrona, o que é muito importante porque o Android não permite trabalhar com a rede no fluxo principal.

Análise da resposta

A análise das respostas do servidor é realizada automaticamente através do mecanismo para descrever o modelo de dados. Eu vou mostrar este processo com código. Aqui, no tópico principal, solicite uma lista de novas imagens de GIF para tabela com 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

Trabalhando com métodos API é descrito na 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();
}

Se serviço do API for escrito com dificuldades, você deve criar adições para análise das respostas usando a classe JsonDeserializer. Às vezes é difícil de entender o que entrou no servidor e o que retornou, nesta situação pode ajudar o HttpLoggingInterceptor. Aqui está o gerador do serviço de trabalho:

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

    // Analisador JSON próprio
    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();

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

A partir da descrição da entrada do muro do aplicativo, precisamos obter apenas texto e um link para o documento. O texto contém a resolução da imagem e o link é necessário para carregar a imagem.

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

Você pode enviar uma imagem para interface de usuário por meios de AsyncTask. E você pode ser um pouco preguiçoso e usar uma biblioteca Fresco (do Facebook) ou Glide (o Google recomenda). Eles vão cuidar de cache e salvar tráfego.

Trabalhar com o Glide:

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

Aqui tudo é simples: a gente fala onde carregamos imagem, onde a inserimos e como trabalhamos com o cache.

Armazenamento de dados

Trabalhando com vídeo, áudio e bancos de dados que são os tópicos extensos e dignos de artigos individuais. Nestas áreas os desenvolvedores de Android têm um bom espaço para criatividade. Como um banco de dados você pode usar o SQLite nativo (pelo jeito os criadores do Telegram recalcularam isso e obtiveram um bom desempenho), se alguém gosta da biblioteca do Realm (em alguns testes ele é o melhor do que o SQLite).

Se estrutura de dados não é muito complexa, você pode salvar nossa matriz de classe como texto (gson) em 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();

E reconstruí-lo sempre que você começar:

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

Não esqueçe de incluir a biblioteca Gson em arquivo gradle:

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

Acabou

Com este artigo eu acabo nosso miniciclo como usar os recursos do VK.com como um backend gratúito para seus aplicativos. Espero que este conhecimento te ajude a escrever uma obra-prima real e economizar um pouco de dinheiro ao mesmo tempo. Boa sorte, e vejo você nas nossas páginas virtuais!

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *