company_banner

Подключаем ВКонтакте SDK для Xamarin.Forms

    В прошлой статье мы рассмотрели вопрос с подключением нативных SDK от Facebook в ваших приложениях на Xamarin.Forms для удобной авторизации пользователей. Сегодня, как и обещали, рассмотрим подключение нативных SDK для социальной сети ВКонтакте. Новый SDK будет подключаться к проекту, который мы описывали в прошлой статье.



    Создаем приложение во ВКонтакте


    В целом, процесс интеграции ВКонтакте будет сильно напоминать работу с Facebook, так что смело заходите на страницу управления приложениями.

    Нажимаем «Создать приложение» и выбираем «Standalone-приложение».



    Далее идём в Настройки и вводим данные о приложении. «Отпечаток сертификата» это Key Hashes, полученные нами в прошлой статье.



    На этом подготовительная часть завершена.

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


    Для Xamarin доступно достаточно много готовых bindings, однако полноценный ВКонтакте SDK появился совсем недавно. Библиотека какое-то время пребывала в стадии beta и сейчас готова к использованию. Большое спасибо Matthew Leibowitz!



    Подключаем в iOS


    Вносим правки в Info.plist. Расширяем CFBundleURLTypes значениями для ВКонтакте:

    <key>CFBundleURLTypes</key>
    <array>
      <dict>
    	  <key>CFBundleURLName</key>
    	  <string>vk5874073</string>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>fb1102463466549096</string>
          <string>vk5874073</string>
        </array>
      </dict>
    </array>

    Добавляем новые LSApplicationQueriesSchemes:

      <string>vk</string> 
      <string>vk-share</string> 
      <string>vkauthorize</string>

    И также новый домен в NSAppTransportSecurity:

    <key>vk.com</key> 
          <dict> 
              <key>NSExceptionRequiresForwardSecrecy</key> 
              <false/> 
              <key>NSIncludesSubdomains</key> 
              <true/> 
              <key>NSExceptionAllowsInsecureHTTPLoads</key> 
              <true/> 
          </dict>

    После этого вносим правки в AppDelegate.cs.

            public override bool FinishedLaunching(UIApplication app, NSDictionary options)
            {
                Xamarin.Forms.Forms.Init();
    
                LoadApplication(new App());
    
                Facebook.CoreKit.Profile.EnableUpdatesOnAccessTokenChange(true);
                Facebook.CoreKit.ApplicationDelegate.SharedInstance.FinishedLaunching(app, options);
                
                VKSdk.Initialize("5874073");
    
                return base.FinishedLaunching(app, options);
            }
    
            public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
            {
                return VKSdk.ProcessOpenUrl(url, sourceApplication) 
                    || Facebook.CoreKit.ApplicationDelegate.SharedInstance.OpenUrl(application, url, sourceApplication, annotation) 
                    || base.OpenUrl(application, url, sourceApplication, annotation);
            }

    На этом первичная инициализация iOS завершена.

    Подключаем в Android


    А вот для Android придётся дополнительно переопределить свой класс Application для корректной инициализации SDK.

        [Application]
        public class MainApplication : Application
        {
            public MainApplication(IntPtr handle, JniHandleOwnership transer)
              :base(handle, transer)
            {
            }
    
            public override void OnCreate()
            {
                base.OnCreate();
                VKSdk.Initialize(this).WithPayments();
            }
        }

    Теперь добавим ID приложения в strings.xml:

      <integer name="com_vk_sdk_AppId">5874073</integer>
      <string name="vk_data_theme">vk5874073</string>


    Добавим немного кода в AndroidManifest.xml между <application ..> … :

        <activity android:name="com.binwell.login.MainActivity" android:exported="true">
          <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="@string/vk_data_theme" />
          </intent-filter>
        </activity>

    И завершим расширением MainActivity:

    protected override async void OnActivityResult(int requestCode, Result resultCode, Intent data)
            {
                bool vkResult;
                var task = VKSdk.OnActivityResultAsync(requestCode, resultCode, data, out vkResult);
    
                if (!vkResult)
                {
                    base.OnActivityResult(requestCode, resultCode, data);
                    AndroidFacebookService.Instance.OnActivityResult(requestCode, (int)resultCode, data);
                    return;
                }
    
                try
                {
                    var token = await task;
                    // Get token
                }
                catch (Exception e)
                {
                    // Handle exception
                }
            }

    Интегрируем с Xamarin.Forms


    По аналогии с Facebook, мы создадим свой интерфейс в PCL-проекте для работы с новым SDK.

        public interface IVkService
        {
            Task<LoginResult> Login();
            void Logout();
        }

    Реализация для iOS


    Для iOS реализация будет выглядеть следующим образом:

    [assembly: Dependency(typeof(AppleVkService))]
    namespace Login.iOS
    {
        public class AppleVkService : NSObject, IVkService, IVKSdkDelegate, IVKSdkUIDelegate
        {
            readonly string[] _permissions = {
                VKPermissions.Email,
                VKPermissions.Offline
            };
    
            LoginResult _loginResult;
            TaskCompletionSource<LoginResult> _completionSource;
    
            public AppleVkService()
            {
                VKSdk.Instance.RegisterDelegate(this);
                VKSdk.Instance.UiDelegate = this;
            }
    
            public Task<LoginResult> Login()
            {
                _completionSource = new TaskCompletionSource<LoginResult>();
                VKSdk.Authorize(_permissions);
                return _completionSource.Task;
            }
    
            public void Logout()
            {
                _loginResult = null;
                _completionSource = null;
            }
    
            [Export("vkSdkTokenHasExpired:")]
            public void TokenHasExpired(VKAccessToken expiredToken)
            {
                VKSdk.Authorize(_permissions);
            }
    
            public new void Dispose()
            {
                VKSdk.Instance.UnregisterDelegate(this);
                VKSdk.Instance.UiDelegate = null;
                SetCancelledResult();
            }
    
            public void AccessAuthorizationFinished(VKAuthorizationResult result)
            {
                if (result?.Token == null)
                    SetErrorResult(result?.Error?.LocalizedDescription ?? @"VK authorization unknown error");
                else
                {
                    _loginResult = new LoginResult
                    {
                        Token = result.Token.AccessToken,
                        UserId = result.Token.UserId,
                        Email = result.Token.Email,
                        ExpireAt = Utils.FromMsDateTime(result.Token.ExpiresIn),
                    };
                    Task.Run(GetUserInfo);
                }
            }
    
            async Task GetUserInfo()
            {
                var request = VKApi.Users.Get(NSDictionary.FromObjectAndKey((NSString)@"photo_400_orig", VKApiConst.Fields));
                var response = await request.ExecuteAsync();
                var users = response.ParsedModel as VKUsersArray;
                var account = users?.FirstObject as VKUser;
                if (account != null && _loginResult != null)
                {
                    _loginResult.FirstName = account.first_name;
                    _loginResult.LastName = account.last_name;
                    _loginResult.ImageUrl = account.photo_400_orig;
                    _loginResult.LoginState = LoginState.Success;
                    SetResult(_loginResult);
                }
                else
                    SetErrorResult(@"Unable to complete the request of user info");
            }
    
            public void UserAuthorizationFailed()
            {
                SetErrorResult(@"VK authorization unknown error");
            }
    
            public void ShouldPresentViewController(UIViewController controller)
            {
                Device.BeginInvokeOnMainThread(() => Utils.GetCurrentViewController().PresentViewController(controller, true, null));
            }
    
            public void NeedCaptchaEnter(VKError captchaError)
            {
                Device.BeginInvokeOnMainThread(() => VKCaptchaViewController.Create(csaptchaError).PresentIn(Utils.GetCurrentViewController()));
            }
    
            void SetCancelledResult()
            {
                SetResult(new LoginResult { LoginState = LoginState.Canceled });
            }
    
            void SetErrorResult(string errorString)
            {
                SetResult(new LoginResult { LoginState = LoginState.Failed, ErrorString = errorString });
            }
    
            void SetResult(LoginResult result)
            {
                _completionSource?.TrySetResult(result);
                _loginResult = null;
                _completionSource = null;
            }
    }

    Реализация для Android


    Для Android тоже ничего необычного.

    [assembly: Dependency(typeof(AndroidVkService))]
    namespace Login.Droid
    {
        public class AndroidVkService : Java.Lang.Object, IVkService
        {
            public static AndroidVkService Instance => DependencyService.Get<IVkService>() as AndroidVkService;
    
            readonly string[] _permissions = {
                VKScope.Email,
                VKScope.Offline
            };
    
            TaskCompletionSource<LoginResult> _completionSource;
            LoginResult _loginResult;
    
            public Task<LoginResult> Login()
            {
                _completionSource = new TaskCompletionSource<LoginResult>();
                VKSdk.Login(Forms.Context as Activity, _permissions);
                return _completionSource.Task;
            }
    
            public void Logout()
            {
                _loginResult = null;
                _completionSource = null;
                VKSdk.Logout();
            }
    
            public void SetUserToken(VKAccessToken token)
            {
                _loginResult = new LoginResult
                {
                    Email = token.Email,
                    Token = token.AccessToken,
                    UserId = token.UserId,
                    ExpireAt = Utils.FromMsDateTime(token.ExpiresIn)
                };
    
                Task.Run(GetUserInfo);
            }
    
            async Task GetUserInfo()
            {
                var request = VKApi.Users.Get(VKParameters.From(VKApiConst.Fields, @"photo_400_orig,"));
                var response = await request.ExecuteAsync();
                var jsonArray = response.Json.OptJSONArray(@"response");
                var account = jsonArray?.GetJSONObject(0);
                if (account != null && _loginResult != null)
                {
                    _loginResult.FirstName = account.OptString(@"first_name");
                    _loginResult.LastName = account.OptString(@"last_name");
                    _loginResult.ImageUrl = account.OptString(@"photo_400_orig");
                    _loginResult.LoginState = LoginState.Success;
                    SetResult(_loginResult);
                }
                else
                    SetErrorResult(@"Unable to complete the request of user info");
            }
    
            public void SetErrorResult(string errorMessage)
            {
                SetResult(new LoginResult { LoginState = LoginState.Failed, ErrorString = errorMessage });
            }
    
            public void SetCanceledResult()
            {
                SetResult(new LoginResult { LoginState = LoginState.Canceled });
            }
    
            void SetResult(LoginResult result)
            {
                _completionSource?.TrySetResult(result);
                _loginResult = null;
                _completionSource = null;
            }
        }
    }

    Подключаем в Xanarin.Forms


    Всё. ВКонтакте работает!



    Напоминаем, что для публикации приложений (чтобы кто-нибудь кроме вас мог авторизоваться) необходимо выполнить дополнительные действия для каждой социальных сети.

    Используем


    Сегодня мы научились авторизовать пользователей, используя нативные ВКонтакте SDK. Если вам требуется более широкий функционал по работе с этой социальной сетью, то рекомендуем изучить примеры самого Matthew.

    Полный код проекта с подключенными нативными SDK для Facebook и ВКонтакте можете найти по адресу.

    В следующей статье мы рассмотрим универсальные способы авторизации пользователей через OAuth в приложениях Xamarin.Forms. Оставайтесь на связи, задавайте ваши вопросы в комментариях и вступайте в группу Xamarin Developers в Telegram.

    Об авторе


    Вячеслав Черников — руководитель отдела разработки компании Binwell. В прошлом — один из Nokia Champion и Qt Certified Specialist, в настоящее время — специалист по платформам Xamarin и Azure. В сферу mobile пришел в 2005 году, с 2008 года занимается разработкой мобильных приложений: начинал с Symbian, Maemo, Meego, Windows Mobile, потом перешел на iOS, Android и Windows Phone.

    Статьи Вячеслава вы также можете прочитать в блоге на Medium.

    Другие статьи автора:


    Xamarin Meetup: Сложные интерфейсы в Xamarin.Forms


    Если вы хотите пообщаться с сообществом Xamarin Developers, а также лично с Вячеславом, 9 марта в Москве пройдёт митап по теме «Сложные интерфейсы в Xamarin Forms».

    Программа митапа:

    18:00 — 18:30 Регистрация
    18:30 — 19:30 Вячеслав Черников // Подключение RecyclerView/UICollectionView для вёрстки сложных интерфейсов
    19:30 — 19:40 Кофе-брейк
    19:40 — 20:40 Юрий Невалённый // Коллекции и списки на Xamarin Forms, паттерны виртуализации и быстрые ячейки (Android, iOS, Windows)

    Участие бесплатно, регистрация обязательна.
    Microsoft
    Microsoft — мировой лидер в области ПО и ИТ-услуг

    Comments 2

      +1
      Для Xamarin доступно достаточно много готовых bindings, однако полноценный ВКонтакте SDK появился совсем недавно.

      К сожалению, не достаточно полноценный. Для использования биндинга в «боевых» проектах, лучше скачать исходники например этого форка. Обязательно проверить, что версия SDK последняя и собрать проект вручную.

      Нужно это сделать хотя бы потому что на версии этого биндинга в NuGet на устройствах с версией iOS 10.1 и 10.1.1 без установленного приложения VK не работала авторизация через VkSdk. Решилось как раз обновлением версии SDK и ручной сборкой
        +1
        Спасибо за статью! Я был один из тех, кто её реквестил :)

        Only users with full accounts can post comments. Log in, please.