asd asdsa f
Статьи

Работа с API ВКонтакте в Android

7 3406

Работа с API ВКонтакте в приложениях под Андроид поможет вашему приложению быстро набрать популярность и повысить лояльность среди пользователей. Как этого добиться?

Во первых, если ваше приложение подразумевает персонализацию, то глупо не предоставить пользователям возможность авторизоваться с помощью популярных соц. сетей. Если ваш пользователь проживает на территории бывшего СССР, то с большой долей вероятности у него уже установлено приложение ВКонтакте. Это значит, авторизация в вашем приложении с помощью VK потребует от него всего одного-двух кликов. При условии, что вы включите такую возможность в ваше приложение, конечно.

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

Итак, вы все еще не внедрили социальные фишки от ВК в ваше приложение или просто не знаете с чего начать? Тогда читайте эту статью и получите исчерпывающую информацию по данному вопросу.

С чего начать работу c API ВКонтакте в Android

Для начала необходимо создать standalone-приложение для vk.com на специальной странице.

Работа с api вконтакте в Android - создание приложения

В разделе «Настройки» нового приложения нужно ввести параметры:

  • Название пакета для Android — главный пакет вашего приложения
  • Main Activity для Android — класс активности, который будет получать ответы от API в метод onActivityResult. Подробнее об этом читайте далее.
  • Отпечаток сертификата для Android — он же certificate fingerprint.

Что такое отпечаток сертификата (certificate fingerprint)?

Apk-файл приложения, прежде чем быть установленным на устройство, должно быть подписан сертификатом разработчика. Даже если приложение еще на этапе отладки и запускается только на эмуляторе, оно все рано должно быть подписано перед загрузкой в эмулятор. IDE (среды разработки), такие как Eclipse ADT или Android Studio, перед запуском приложения автоматически подписывают приложения с помощью т.н. debug-сертификата (отладочного сертификата). Данный сертификат находится обычно в домашней директории пользователя:

  • ~/.android/ в Linux,
  • C:\Documents and Settings\\.android\ в Windows XP
  • C:\Users\\.android\ в Windows Vista, Windows 7 и Windows 8

Система сборки автоматически подписывает собранный apk и затем устанавливает его на устройство.

Чтобы опубликовать приложение в Google Play, debug-сертификат не подойдет. Тогда нужен т.н. release-сертификат. Этот сертификат будет содержать основные сведения о вас — разработчике приложения.

Для примеров в данной статье можно воспользоваться debug-сертификатом.

Чтобы получить отпечаток сертификата, в консоли выполните такую команду:

keytool -exportcert -alias androiddebugkey -keystore %HOMEDRIVE%\%HOMEPATH%\.android\debug.keystore -list -v

Путь %HOMEDRIVE%\%HOMEPATH% задает домашнюю папку пользователя в Windows. Если вы используете другу систему или по какой-то причине домашняя директория отличается от стандартной (%HOMEDRIVE%\%HOMEPATH%), то просто найдите, где у вас находится файл debug.keystore и укажите путь до него.

Обратите внимание, что алиас (alias) используется стандартный — androiddebugkey. У release-сертификата будет, скорее всего, другой алиас.

Что такое алиас и кейстор?

Файл .keystore, созданный утилитой keytool, содержит записи пар открытых и закрытых ключей. Каждая такая пара идентифицируется уникальным псевдонимом, или, что то же самое — алиасом.

То есть:

Запись в keystore = пара открытый\закрытый ключ = идентифицируется алиасом

Кейстор защищает каждый закрытый ключ индивидуальным паролем. Целостность всего кейстора также защищена паролем.

При экспорте приложения в apk ваша IDE запрашивает, какой кейстор выбрать. Затем предлагается выбрать алиас (пару открытый-закрыты ключ). Далее, нужно ввести пароли для кейстора и алиаса. После этого apk будет подписан с помощью закрытого ключа и сертификат (вместе с открытым ключем) будет добавлен в apk.

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

Результат выполнения данной команды будет выглядеть примерно так:

keytool -exportcert -alias androiddebugkey -keystore %HOMEDRIVE%\%HOMEPATH%\.android\debug.keystore -list -v
Enter keystore password:

*****************  WARNING WARNING WARNING  *****************
* The integrity of the information stored in your keystore  *
* has NOT been verified!  In order to verify its integrity, *
* you must provide your keystore password.                  *
*****************  WARNING WARNING WARNING  *****************

Alias name: androiddebugkey
Creation date: 04.09.2013
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Android Debug, O=Android, C=US
Issuer: CN=Android Debug, O=Android, C=US
Serial number: 2f87223
Valid from: Wed Sep 04 21:43:03 YEKT 2013 until: Fri Aug 28 21:43:03 YEKT 2043
Certificate fingerprints:
         MD5:  F2:0C:92:92:87:37:2E:85:BD:B6:02:37:F5:9E:FF:37
         SHA1: 06:37:20:6E:5E:36:2F:02:C3:C8:26:66:AF:BF:BF:7F:74:04:03:D4
         SHA256: 76:CF:67:25:A2:1C:BF:4D:90:46:A0:A5:E8:4B:C2:E6:F3:6C:66:DC:01:F2:5E:86:7F:E3:55:B3:02:20:A3:DD
         Signature algorithm name: SHA256withRSA
         Version: 3

Extensions:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 75 BD 20 B4 75 12 B1 6D   1C 4F B3 91 A0 A8 20 13  u. .u..m.O.... .
0010: 49 35 06 2D                                        I5.-
]
]

Строка SHA1: 06:37:20:6E…. как раз и есть отпечаток сертификата. Он представляет собой хеш, полученный по алгоритму SHA-1.

Теперь нужно удалить двоеточия, так, чтобы осталась только строка вида 0637206E5E362F02C3C82666AFBFBF7F740403D4. Вставьте эту строку в поле «Отпечаток сертификата для Android»:

Работа с api вконтакте в Андроид

После того, как standalone-приложение создано и настроено, можно приступить к подключению SDK к вашему android-приложению.

Подключение SDK ВКонтакте к приложению для Android

Сразу разочарую пользователей Eclipse. Все дальнейшие инструкции будут приведены для Android Studio. Эта IDE является действительно большим шагом вперед по сравнению с ADT, поэтому я рекомендую использовать именно ее для всех новых проектов.

Из основных преимуществ можно отметить:

  • более продвинутая система сборки gradle
  • более совершенный визуальный редактор интерфейса
  • более полные и удобные подсказки при редактировании xml-разметки

Скачать установщик Android Studio можно по адресу https://developer.android.com/sdk/installing/studio.html

Если у вас еще нет проекта, то создайте новый. Я назвал свой проект VkApiDemo, основной пакет — ru.interosite.vkapidemo. Везде, где в примерах используются эти значения, используйте свои значения.

В папке app откройте файл build.gradle:

interosite_ru_vkapi_android_add_sdk

Если вы хотите подключить SDK в виде артефакта (jar-файл), то в dependencies добавьте строку «compile ‘com.vk:androidsdk:+'»:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:20.+'
    compile 'com.vk:androidsdk:+'
}

Это означает, что при компиляции приложения будет добавлена последняя доступная версия jar androidsdk. При таком способе подключения вы, конечно, не сможете отлаживать код SDK.

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

Скачайте мастер-ветку из официального git-репозитория.

Распакуйте и скопируйте папку vksdk_library из архива в корень вашего проекта (рядом с папкой app):

interosite_ru_vkapi_android_add_source_sdk

Откройте файл settings.gradle в корне проекта и добавьте туда проект vksdk_library в качестве модуля:

include ':vksdk_library',':app' 

Теперь вместо строки compile ‘com.vk:androidsdk:+’ в файл app/build.gradle вставьте

compile project(':vksdk_library') 

Так проект объявляет зависимость от модуля vksdk_library.

После изменения в файлах gradle IDE предложит сделать синхронизацию проекта с системой сборки:

interosite_ru_vkapi_android_gradle_sync_now

Нажмите Sync Now. После этого все классы SDK будут доступны инспектору кода в IDE.

Основы работы с SDK ВКонтакте для Android

Работа SDK ВКонтакте основана на жизненном цикле главной активности приложения. В SDK есть статический класс VKUIHelper со следующим набором методов:

  • onCreate(Activity) — передать текущую активность
  • onResume(Activity) — на данный момент делает то же, что и onCreate
  • onActivityResult(Activity, int, int, Intent) — обрабатывает ответы с кодом VK_SDK_REQUEST_CODE. Тут происходит проверка правильности данных авторизации и прочие ответы от окон приложения вконтакте
  • onDestroy(Activity) — очищает ссылку на текущую активность

Все эти методы должны быть вызваны в одноименных методах жизненного цикла активности.

Кроме того, перед использованием API, необходимо инициализировать SDK с помощью метода

VKSdk.initialize(VKSdkListener, String appId);

VKSdkListener — абстрактный класс, в котором нужно реализовать три метода:

  • void onCaptchaError(VKError captchaError) — была неверно введена капча
  • void onTokenExpired(VKAccessToken expiredToken) — токен (ключ сессии) устарел
  • void onAccessDenied(VKError authorizationError) — appId это id приложения, который присваивается автоматически при создании. Его нужно скопировать со страницы настроек приложения.

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

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Инициализация приложения...
        VKSdk.initialize(sdkListener, VK_APP_ID);
        VKUIHelper.onCreate(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        VKUIHelper.onResume(this);
        //....
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        VKUIHelper.onActivityResult(this, requestCode, resultCode, data);
        //....
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        VKUIHelper.onDestroy(this);
        //....
    }
}

Прежде чем начать работу в API, необходимо пройти авторизацию. Для проверки того, зарегистрирован пользователь или нет, можно воспользоваться статическим методом VKSdk.isLoggedIn(). Однако, этот метод всего-лишь проверяет наличие активного токена с не истекшим сроком использования.

Чтобы автоматически подгрузить последний токен из постоянного хранилища и одновременно проверить его валидность, можно использовать метод VKSdk.wakeUpSession(). Этот метод рекомендуется использовать всегда, т.к. он гарантирует восстановление сессии даже после перезапуска приложения.

Пример приложения с использованием API ВКонтакте

Чтобы показать, как использовать SDK в реальной жизни, разработаем простое приложение, которое получает список друзей и их дни рождения.

Весь пользовательский интерфейс приложения управляется единственным классом активности MainActivity.
Если изначально пользователь не авторизован, то отображается кнопка, при нажатии на которую запускается авторизация:

Использование API ВКонтакте в Android - aвторизация

Далее, управление приложением берет на себя SDK. Если у пользователя установлено приложение ВК, то запускается оно. Если приложения нет, то запускается WebView, в котором открывается страница авторизации на сайте ВК. После ввода логина и пароля, появится диалог подтверждения запрашиваемых разрешений:

 Использование API ВКонтакте в Android - разрешения для приложения

После принятия разрешений, автоматически выполняется запрос на получение списка друзей. Если запрос выполнен успешно, то список друзей и их дней рождения будет выведен в лист:

 Использование API ВКонтакте в Android - список пользователе

После разработки базовой функциональности, вы также узнаете, как реализовать диалог «поделиться». При клике на любом из пользователей можно будет отправить сообщение об этом действии на свою страничку в соц. сети:

 Использование API ВКонтакте в Android - поделиться

Код главной активности выглядит так:

Посмотреть код MainAvtivity.java

package ru.interosite.vkapidemo;

import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.TextView;

import com.vk.sdk.VKAccessToken;
import com.vk.sdk.VKScope;
import com.vk.sdk.VKSdk;
import com.vk.sdk.VKSdkListener;
import com.vk.sdk.VKUIHelper;
import com.vk.sdk.api.VKApi;
import com.vk.sdk.api.VKApiConst;
import com.vk.sdk.api.VKError;
import com.vk.sdk.api.VKParameters;
import com.vk.sdk.api.VKRequest;
import com.vk.sdk.api.VKResponse;
import com.vk.sdk.api.model.VKApiUserFull;
import com.vk.sdk.api.model.VKUsersArray;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by cyrusmith
 * All rights reserved
 * http://interosite.ru
 * info@interosite.ru
 */
public class MainActivity extends ListActivity {

    private static final String VK_APP_ID = "4520250";

    private final VKSdkListener sdkListener = new VKSdkListener() {

        @Override
        public void onAcceptUserToken(VKAccessToken token) {
            Log.d("VkDemoApp", "onAcceptUserToken " + token);
            startLoading();
        }

        @Override
        public void onReceiveNewToken(VKAccessToken newToken) {
            Log.d("VkDemoApp", "onReceiveNewToken " + newToken);
            startLoading();
        }

        @Override
        public void onRenewAccessToken(VKAccessToken token) {
            Log.d("VkDemoApp", "onRenewAccessToken " + token);
            startLoading();
        }

        @Override
        public void onCaptchaError(VKError captchaError) {
            Log.d("VkDemoApp", "onCaptchaError " + captchaError);
        }

        @Override
        public void onTokenExpired(VKAccessToken expiredToken) {
            Log.d("VkDemoApp", "onTokenExpired " + expiredToken);
        }

        @Override
        public void onAccessDenied(VKError authorizationError) {
            Log.d("VkDemoApp", "onAccessDenied " + authorizationError);
        }

    };

    private VKRequest currentRequest;
    private Button loginButton;

    private final List<User> users = new ArrayList<User>();
    private ArrayAdapter<User> listAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        listAdapter = new ArrayAdapter<User>(this, android.R.layout.simple_list_item_2, android.R.id.text1, users) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {

                View view = super.getView(position, convertView, parent);

                final User user = getItem(position);

                ((TextView) view.findViewById(android.R.id.text1)).setText(user.getName());

                String birthDateStr = "Не задано";

                DateTime dt = user.getBirthDate();

                if (dt != null) {
                    birthDateStr = dt.toString(DateTimeFormat.forPattern(user.getDateFormat()));
                }

                ((TextView) view.findViewById(android.R.id.text2)).setText(birthDateStr);
                return view;

            }
        };
        setListAdapter(listAdapter);

        VKSdk.initialize(sdkListener, VK_APP_ID);

        VKUIHelper.onCreate(this);

        loginButton = (Button) findViewById(R.id.login_button);
        loginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                VKSdk.authorize(VKScope.FRIENDS);
            }
        });

        if (VKSdk.wakeUpSession()) {
            startLoading();
        } else {
            loginButton.setVisibility(View.VISIBLE);
        }

    }

    @Override
    protected void onResume() {
        super.onResume();
        VKUIHelper.onResume(this);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        VKUIHelper.onActivityResult(this, requestCode, resultCode, data);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        VKUIHelper.onDestroy(this);
        if (currentRequest != null) {
            currentRequest.cancel();
        }
    }

    private void startLoading() {
        loginButton.setVisibility(View.GONE);
        if (currentRequest != null) {
            currentRequest.cancel();
        }
        currentRequest = VKApi.friends().get(VKParameters.from(VKApiConst.FIELDS, "id,first_name,last_name,bdate"));
        currentRequest.executeWithListener(new VKRequest.VKRequestListener() {
            @Override
            public void onComplete(VKResponse response) {
                super.onComplete(response);
                Log.d("VkDemoApp", "onComplete " + response);

                VKUsersArray usersArray = (VKUsersArray) response.parsedModel;
                users.clear();
                final String[] formats = new String[]{"dd.MM.yyyy", "dd.MM"};

                for (VKApiUserFull userFull : usersArray) {
                    DateTime birthDate = null;
                    String format = null;
                    if (!TextUtils.isEmpty(userFull.bdate)) {
                        for (int i = 0; i < formats.length; i++) {
                            format = formats[i];
                            try {
                                birthDate = DateTimeFormat.forPattern(format).parseDateTime(userFull.bdate);
                            } catch (Exception ignored) {
                            }
                            if (birthDate != null) {
                                break;
                            }
                        }

                    }
                    users.add(new User(userFull.toString(), birthDate, format));
                }
                listAdapter.notifyDataSetChanged();
            }

            @Override
            public void attemptFailed(VKRequest request, int attemptNumber, int totalAttempts) {
                super.attemptFailed(request, attemptNumber, totalAttempts);
                Log.d("VkDemoApp", "attemptFailed " + request + " " + attemptNumber + " " + totalAttempts);
            }

            @Override
            public void onError(VKError error) {
                super.onError(error);
                Log.d("VkDemoApp", "onError: " + error);
            }

            @Override
            public void onProgress(VKRequest.VKProgressType progressType, long bytesLoaded, long bytesTotal) {
                super.onProgress(progressType, bytesLoaded, bytesTotal);
                Log.d("VkDemoApp", "onProgress " + progressType + " " + bytesLoaded + " " + bytesTotal);
            }
        });
    }

}

xml-разметка интерфейса:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/login_button"
        android:visibility="gone"
        android:drawableLeft="@drawable/ic_ab_app"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/title_login"/>

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

Кнопка login_button инициализирует запуск процесса авторизации. По умолчанию кнопка скрыта и появляется только если пользователь не авторизован. Список друзей отображается в списке @android:id/list.

Также, понадобится вспомогательный класс User для хранения данных о пользователе:

package ru.interosite.vkapidemo;

import org.joda.time.DateTime;

/**
 * Created by cyrusmith
 * All rights reserved
 * http://interosite.ru
 * info@interosite.ru
 */
public class User {

    private final String name;
    private final DateTime birthDate;
    private final String dateFormat;

    public User(String name, DateTime birthDate, String dateFormat) {
        this.name = name;
        this.birthDate = birthDate;
        this.dateFormat = dateFormat;
    }

    public DateTime getBirthDate() {
        return birthDate;
    }

    public String getName() {
        return name;
    }

    public String getDateFormat() {
        return dateFormat;
    }
}

Как видите, в приложении используется класс DateTime из библиотеки JodaTime, которая уже давно стала стандартом де-факто работы с датами в Java. Подключить эту библиотеку можно с помощью такой зависимости в файле build.gradle:

dependencies {
    ...
    compile 'net.danlew:android.joda:2.4.0'
}

Рассмотрим код MainActivity по порядку.

Инициализация, метод onCreate

После установки главной View методом setContentView создаем адаптер для списка:


        listAdapter = new ArrayAdapter<User>(this, android.R.layout.simple_list_item_2, android.R.id.text1, users) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {

                View view = super.getView(position, convertView, parent);

                final User user = getItem(position);

                ((TextView) view.findViewById(android.R.id.text1)).setText(user.getName());

                String birthDateStr = "Не задано";

                DateTime dt = user.getBirthDate();

                if (dt != null) {
                    birthDateStr = dt.toString(DateTimeFormat.forPattern(user.getDateFormat()));
                }

                ((TextView) view.findViewById(android.R.id.text2)).setText(birthDateStr);
                return view;

            }
        };
        setListAdapter(listAdapter);

Метод getView переопределен , т.к. необходимо вывести дату рождения пользователя в правильном формате. Дело в том, что ВКонтакте возвращает дару рождения (если задана) либо в формате «dd.MM.yyyy», либо как «dd.MM» (без указания года). Это факт и нужно учесть при отображении дне рождения. Поэтому, вместе с самой датой мы сохраняем и ее формат.

Далее, происходит инициализация sdk и попытка восстановить предыдущую сессию:

        VKSdk.initialize(sdkListener, VK_APP_ID);

        VKUIHelper.onCreate(this);

        loginButton = (Button) findViewById(R.id.login_button);
        loginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                VKSdk.authorize(VKScope.FRIENDS);
            }
        });

        if (VKSdk.wakeUpSession()) {
            startLoading();
        } else {
            loginButton.setVisibility(View.VISIBLE);
        }

Если сессию восстановить не удалось, то отображается кнопка для авторизации. Нажатие на кнопку запускает процесс аутентификации:

VKSdk.authorize(VKScope.FRIENDS);

Метод authorize принимает произвольное количество аргументов, каждый из которых задает требуемые разрешения для приложения. Сейчас требуется только разрешение для получения друзей. В дальнейшем будет нужно еще одно разрешение для функциональности диалога «поделиться».

Метод startLoading осуществляет запрос на получение списка друзей.

Сначала создается объект VKRequest:

currentRequest = VKApi.friends().get(VKParameters.from(VKApiConst.FIELDS, "id,first_name,last_name,bdate"));

Такие методы класса VKApi, как friends(), wall(), photos(), groups() и т.д. создают соответствующие объекты для запросов в разным элементам соц. сети. Возвращаемые объекты VKApiFriends, VKApiWall, VKApiPhotos являются всего лишь удобными обертками над объектом VKRequest.

Например, чтобы создать запрос на получение пользователей, можно инициализировать объект напрямую:

currentRequest  = new VKRequest("users.get", VKParameters.from(VKApiConst.FIELDS, "id,first_name,last_name,bdate"), VKRequest.HttpMethod.GET, VKUsersArray.class);

Далее, нужно выполнить запрос, чтобы начать загрузку пользователей. Для этого есть метод VKRequest.executeWithListener(VKRequest.VKRequestListener). Он запускает асинхронный запрос, результата которого можно получить с помощью слушателя VKRequestListener. Каждый из методов слушателя вызывается в главном потоке приложения.

В нашем приложении запрос обрабатывается следующим образом:


        currentRequest.executeWithListener(new VKRequest.VKRequestListener() {
            @Override
            public void onComplete(VKResponse response) {
                super.onComplete(response);
                Log.d("VkDemoApp", "onComplete " + response);

                VKUsersArray usersArray = (VKUsersArray) response.parsedModel;
                users.clear();
                final String[] formats = new String[]{"dd.MM.yyyy", "dd.MM"};

                for (VKApiUserFull userFull : usersArray) {
                    DateTime birthDate = null;
                    String format = null;
                    if (!TextUtils.isEmpty(userFull.bdate)) {
                        for (int i = 0; i < formats.length; i++) {
                            format = formats[i];
                            try {
                                birthDate = DateTimeFormat.forPattern(format).parseDateTime(userFull.bdate);
                            } catch (Exception ignored) {
                            }
                            if (birthDate != null) {
                                break;
                            }
                        }

                    }
                    users.add(new User(userFull.toString(), birthDate, format));
                }
                listAdapter.notifyDataSetChanged();
            }

            @Override
            public void attemptFailed(VKRequest request, int attemptNumber, int totalAttempts) {
                super.attemptFailed(request, attemptNumber, totalAttempts);
                Log.d("VkDemoApp", "attemptFailed " + request + " " + attemptNumber + " " + totalAttempts);
            }

            @Override
            public void onError(VKError error) {
                super.onError(error);
                Log.d("VkDemoApp", "onError: " + error);
            }

            @Override
            public void onProgress(VKRequest.VKProgressType progressType, long bytesLoaded, long bytesTotal) {
                super.onProgress(progressType, bytesLoaded, bytesTotal);
                Log.d("VkDemoApp", "onProgress " + progressType + " " + bytesLoaded + " " + bytesTotal);
            }
        });

Вся полезная работа совершается в методе onComplete, который вызывается при успешном выполнении запроса. Тут извлекаются данные из объекта VKResponse и затем передаются в список users.

Поле parsedModel объекта VKResponse содержит объект, соответствующий запросу. В нашем случае это VKUsersArray.

Также, вы можете получить напрямую объект JSONObject из поля json или просто строку ответа из поля responseString. Конечно, делать это приходится только в экзотических случаях.

После заполнения списка пользователей даем знать адаптеру листа о том, что данные обновились:

listAdapter.notifyDataSetChanged();

Большинство запросов к API выполняются похожим образом. Посмотрите исходный код класса VKApi, чтобы увидеть, какие методы доступны для получения различных объектов API.

Также, полную документацию по методам API вы, конечно, можете найти на сайте ВК.

Дополнительные возможности SDK

Параллельное (пакетное) выполнение запросов

SDK позволяет выполнять несколько запросов одновременно. Для этого нужно использовать объект VKBatchRequest. Он принимает произвольное количество объектов-запросов и выполняет из параллельно в разных потоках:

 VKBatchRequest batch = new VKBatchRequest(request1, request2, request3, request4);
                    batch.executeWithListener(new VKBatchRequestListener() {
                        @Override
                        public void onComplete(VKResponse[] responses) {
                            super.onComplete(responses);
                            //обрабатываем responses
                        }

                        @Override
                        public void onError(VKError error) {
                            //обрабатываем error;
                        }
                    });

Метод VKBatchRequestListener.onComplete выполняется только тогда, когда будут завершены все запросы. При этом будут вызваны и метод VKRequestListener.onComplete() у каждого из запросов при их завершении.

Метод VKBatchRequestListener.onError вызывается один раз в случае когда любой из запросов завершился с ошибкой. Тогда вся пакетная задача останавливается.

Диалог «Поделиться»

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

В SDK за эту функциональность отвечает класс VKShareDialog. В помощью него можно поделиться текстовым контентом, фотографиями и любыми другими изображениями. Можно добавить ссылку на любой контент в сети (например, на страницу приложения).

Пример использования:

 
        final Bitmap b = loadFotoFromResourcesOrFromFileSystem(); 
        VKPhotoArray photos = new VKPhotoArray();
        photos.add(new VKApiPhoto("photo-47594638_689374739"));
        new VKShareDialog()
                .setText("Я хочу поделиться контентом из приложения XYZ")
                .setUploadedPhotos(photos)
                .setAttachmentImages(new VKUploadImage[]{
                        new VKUploadImage(b, VKImageParameters.pngImage())
                })
                .setAttachmentLink("Posted from app XYZ", "https://xyzapp.com")
                .setShareDialogListener(new VKShareDialog.VKShareDialogListener() {
                    @Override
                    public void onVkShareComplete(int postId) {
                        //контент отправлен
                    }

                    @Override
                    public void onVkShareCancel() {
                        //отмена
                    }
                }).show(getFragmentManager(), "VK_SHARE_DIALOG");        


Нужно отметить, что в этом случае нужно использовать Support Library, т.к. VKShareDialog наследуется от android.support.v4.app.DialogFragment, а не от android.app.DialogFragment.

В связи с этим, активность должны быть реализована по другому. В частности, она должна наследоваться от FragmentActivity. Список пользователей теперь придется выводить с помощью ListFragment.

В качестве самостоятельного упражнения изучите код модифицированного приложения с использованием Support Library и с поддержкой VKShareDialog:

Посмотреть код с поддержкой VKShareDialog

xml-разметка для главной активности main.xml:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/main_fragment"
        android:name="ru.interosite.vkapidemo.MainUiFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

xml-разметка для фрагмента:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/login_button"
        android:visibility="gone"
        android:drawableLeft="@drawable/ic_ab_app"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/title_login"/>

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

Главная активность MainActivity.java:

package ru.interosite.vkapidemo;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.text.TextUtils;
import android.util.Log;

import com.vk.sdk.VKAccessToken;
import com.vk.sdk.VKScope;
import com.vk.sdk.VKSdk;
import com.vk.sdk.VKSdkListener;
import com.vk.sdk.VKUIHelper;
import com.vk.sdk.api.VKApi;
import com.vk.sdk.api.VKApiConst;
import com.vk.sdk.api.VKError;
import com.vk.sdk.api.VKParameters;
import com.vk.sdk.api.VKRequest;
import com.vk.sdk.api.VKResponse;
import com.vk.sdk.api.model.VKApiUserFull;
import com.vk.sdk.api.model.VKUsersArray;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by cyrusmith
 * All rights reserved
 * http://interosite.ru
 * info@interosite.ru
 */
public class MainActivity extends FragmentActivity implements MainUiFragment.Callback {

    private static final String VK_APP_ID = "4520250";

    private final VKSdkListener sdkListener = new VKSdkListener() {

        @Override
        public void onAcceptUserToken(VKAccessToken token) {
            Log.d("VkDemoApp", "onAcceptUserToken " + token);
            startLoading();
        }

        @Override
        public void onReceiveNewToken(VKAccessToken newToken) {
            Log.d("VkDemoApp", "onReceiveNewToken " + newToken);
            startLoading();
        }

        @Override
        public void onRenewAccessToken(VKAccessToken token) {
            Log.d("VkDemoApp", "onRenewAccessToken " + token);
            startLoading();
        }

        @Override
        public void onCaptchaError(VKError captchaError) {
            Log.d("VkDemoApp", "onCaptchaError " + captchaError);
        }

        @Override
        public void onTokenExpired(VKAccessToken expiredToken) {
            Log.d("VkDemoApp", "onTokenExpired " + expiredToken);
        }

        @Override
        public void onAccessDenied(VKError authorizationError) {
            Log.d("VkDemoApp", "onAccessDenied " + authorizationError);
        }

    };

    private VKRequest currentRequest;

    private MainUiFragment uiFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        uiFragment = (MainUiFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);

        VKSdk.initialize(sdkListener, VK_APP_ID);

        VKUIHelper.onCreate(this);
        if (VKSdk.wakeUpSession()) {
            startLoading();
        } else {
            uiFragment.setLoginVisible(true);
        }

    }

    @Override
    protected void onResume() {
        super.onResume();
        VKUIHelper.onResume(this);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        VKUIHelper.onActivityResult(this, requestCode, resultCode, data);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        VKUIHelper.onDestroy(this);
        if (currentRequest != null) {
            currentRequest.cancel();
        }
    }

    private void startLoading() {
        uiFragment.setLoginVisible(false);
        if (currentRequest != null) {
            currentRequest.cancel();
        }
        currentRequest = VKApi.friends().get(VKParameters.from(VKApiConst.FIELDS, "id,first_name,last_name,bdate"));
        currentRequest.executeWithListener(new VKRequest.VKRequestListener() {
            @Override
            public void onComplete(VKResponse response) {
                super.onComplete(response);
                Log.d("VkDemoApp", "onComplete " + response);

                final List<User> users = new ArrayList<User>();

                VKUsersArray usersArray = (VKUsersArray) response.parsedModel;
                final String[] formats = new String[]{"dd.MM.yyyy", "dd.MM"};

                for (VKApiUserFull userFull : usersArray) {
                    DateTime birthDate = null;
                    String format = null;
                    if (!TextUtils.isEmpty(userFull.bdate)) {
                        for (int i = 0; i < formats.length; i++) {
                            format = formats[i];
                            try {
                                birthDate = DateTimeFormat.forPattern(format).parseDateTime(userFull.bdate);
                            } catch (Exception ignored) {
                            }
                            if (birthDate != null) {
                                break;
                            }
                        }

                    }
                    users.add(new User(userFull.toString(), birthDate, format));
                }

                uiFragment.setUsers(users);

            }

            @Override
            public void attemptFailed(VKRequest request, int attemptNumber, int totalAttempts) {
                super.attemptFailed(request, attemptNumber, totalAttempts);
                Log.d("VkDemoApp", "attemptFailed " + request + " " + attemptNumber + " " + totalAttempts);
            }

            @Override
            public void onError(VKError error) {
                super.onError(error);
                Log.d("VkDemoApp", "onError: " + error);
            }

            @Override
            public void onProgress(VKRequest.VKProgressType progressType, long bytesLoaded, long bytesTotal) {
                super.onProgress(progressType, bytesLoaded, bytesTotal);
                Log.d("VkDemoApp", "onProgress " + progressType + " " + bytesLoaded + " " + bytesTotal);
            }
        });
    }

    @Override
    public void onLoginButtonClick() {
        VKSdk.authorize(VKScope.FRIENDS, VKScope.WALL);
    }

}

UI-фрагмент:

package ru.interosite.vkapidemo;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

import com.vk.sdk.dialogs.VKShareDialog;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by cyrusmith
 * All rights reserved
 * http://interosite.ru
 * info@interosite.ru
 */
public class MainUiFragment extends ListFragment {

    interface Callback {
        void onLoginButtonClick();
    }

    private final List<User> users = new ArrayList<User>();
    private ArrayAdapter<User> listAdapter;

    private Callback callback;
    private Button loginButton;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        callback = (Callback) activity;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        listAdapter = new ArrayAdapter<User>(getActivity(), android.R.layout.simple_list_item_2, android.R.id.text1, users) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {

                View view = super.getView(position, convertView, parent);

                final User user = getItem(position);

                ((TextView) view.findViewById(android.R.id.text1)).setText(user.getName());

                String birthDateStr = "Не задано";

                DateTime dt = user.getBirthDate();

                if (dt != null) {
                    birthDateStr = dt.toString(DateTimeFormat.forPattern(user.getDateFormat()));
                }

                ((TextView) view.findViewById(android.R.id.text2)).setText(birthDateStr);
                return view;

            }
        };
        setListAdapter(listAdapter);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.main_fragment, null);
        loginButton = (Button) view.findViewById(R.id.login_button);
        loginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                callback.onLoginButtonClick();
            }
        });

        return view;
    }

    public void setUsers(List<User> users) {
        this.users.clear();
        this.users.addAll(users);
        if (listAdapter != null)
            listAdapter.notifyDataSetChanged();
    }

    public void setLoginVisible(boolean loginVisible) {
        loginButton.setVisibility(loginVisible ? View.VISIBLE : View.GONE);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);

        final User user = listAdapter.getItem(position);

        new VKShareDialog()
                .setText("Я только что кликнул по " + user.getName())
                .setAttachmentLink("Отправлено из VkApiDemoApp", "https://interosite.ru")
                .setShareDialogListener(new VKShareDialog.VKShareDialogListener() {
                    @Override
                    public void onVkShareComplete(int postId) {
                        //контент отправлен
                    }

                    @Override
                    public void onVkShareCancel() {
                        //отмена
                    }
                }).show(getFragmentManager(), "VK_SHARE_DIALOG");
    }

}

Заключение

В этой статье вы узнали, как подключить SDK ВКонтакте в проект приложения для Android. Научились основам работы с API и разработали простое приложение, взаимодействующее с сервисами вконтакте.

Спасибо за прочтение и удачного программирования!

P.S.
Расскажите в комментариях, были ли у вас трудности с подключением и использованием SDK ВКонтакте для Android?

Если статья вам понравилась и была полезной, то поделитесь ссылкой на нее в любой из соц. сетей.

About the author / 

admin

  • Дмитрий Зайцев

    Спасибо!Это настоящий клад

  • rim rim

    спасибо. хорошая статья

  • Николай Мурин

    Все сделал также. Но при входе получил ошибку {«error»: «invalid_request», «error_description» : «sdk_package id incorrect»}

    • y0rsh

      Та же проблема! Отпиши, пожалуйста, если решил. Через браузер всё отлично

  • Zhambulable .

    ошибка net::err:cache:miss

    хотя добавил

  • Денис Шовгеня

    Получал ошибку, null pointer exception. поменял местами VKUIHelper.onCreate(this);
    VKSdk.initialize() и заработало.

  • Alexandr Dolgov

    Спасибо комментаторам Денис Шовгеня и Zhambulable, по их советам добавил в файл AndroidManifest.xml внутрь элемента первым элементом и в классе MainActivity в методе onCreate сделал так чтобы сначала шла строчка VKUIHelper.onCreate(this); а уже за ней VKSdk.initialize(sdkListener, VK_APP_ID);. И после этого все наконец заработало. Авторы статьи, внесите пожалуйста соответствующие правки в статью.