[Из песочницы] Подключаем FB, VK, G+ в Android. Версия Light
Встала передо мной задача — сделать постинг ссылок из Андроида в пару-тройку соцсетей. Причем, максимально простой и легкий — чтобы не плодить сущности и как можно меньше заморачиваться с токенами, сессиями и прочая. Задача, действительно, минимум — только размещение ссылки в собственном аккаунте пользователя. Если к ссылке можно легко добавить описания или картинки — сделать, но не упираться.В силу разных причин были выбраны Facebook, Vkontakte и Google+. Планировала добавить Twitter, но его Fabric к тому времени еще не вышел, а использовать стороннюю библиотеку не хотелось (см. п.2 ниже). Позже добавлю.
В итоге, задача для этих трех соцсетей получилась следующей:
Максимально простой программный интерфейс постинга ссылок. Использование только нативных SDK (из тех соображений, что эти знания пригодятся в дальнейшем). Минимум кода — только самое необходимое для работы. Работать все должно вне зависимости от наличия у пользователя установленного клиента соцсети. Но если он есть — использовать диалоговые окна клиента. Пользователю должно выводиться сообщение об успешном или нет размещении записи. Должна быть возможность программно реагировать на успешное размещение записи. Архитектура решенияВариант с отдельной библиотекой в конечном итоге оказался чересчур громоздким — итоговый код составил порядка 250 строк вместе с комментариями и пустыми строками. Причем, все инкапсулировалось в один класс, использовать который достаточно просто.Поскольку ряд объектов из SDK все же пришлось встраивать в жизненный цикл, то нужен был некий контейнер — или Activity, или Fragment. Я выбрала решение с abstract Activity, т.к. мне нужно было вешать команды постинга на кнопку ActionBar. Причем, делать это для нескольких Activity с разными фрагментами внутри.
В abstract Activity я зашила весь код управления постингом, а наружу выставила 3 метода — по одному для каждой сетки. От этой самой abstract Activity потом унаследовала остальные Activity приложения, в которых были нужны соцсети. В них доступ к нужному коду свелся к вызову метода из родительского класса.
Все, что здесь описано, наверняка можно сделать и с фрагментом. Хотя где-то я наталкивалась на сообщение, что встраивание в жизненный цикл все равно нужно делать для родительской Activity. Не знаю, не пробовала.
Итак…
Чтобы описанный ниже код заработал, нужно подключить в проект для каждой соцсети свой SDK и зарегистрировать приложение в разделе разработчиков. Все описано здесь:
При разработке использовались описания SDK на родных сайтах соцсетей, статьи на Хабре и SO, код библиотеки ASNE (спасибо автору!)Google+ Самым простым кодом логично оказалось решение для G+. Вся работа с аутентификацией и сессиями уже встроена в систему. Нужное диалоговое окно (нативное от клиента или веб, если клиента нет) выбирается само, и само же сообщает об успешном постинге. Вот в последнем и крылась единственная засада — реализация п.6 требований. Для отслеживания успешности публикации пришлось добавить одну константу и один условный оператор в onActivityResult: private static final int GOOGLEPLUS_REQUEST_CODE = 1001;
protected void onActivityResult (int requestCode, int resultCode, Intent data) { … if ((requestCode == GOOGLEPLUS_REQUEST_CODE) && (resultCode == -1)) { //Do something if success } } Сама процедура постинга: /** * Publish link in Google+ * @param text — message about link (may be changed or deleted by user) * @param link — http:// etc */ public final void googleplusPublish (String text, String link) { Intent shareIntent = new PlusShare.Builder (this) .setType («text/plain») .setText (text) .setContentUrl (Uri.parse (link)) .getIntent (); startActivityForResult (shareIntent, GOOGLEPLUS_REQUEST_CODE); } FaceBook C Фейсбуком пришлось повозиться подольше, и не все требования удалось реализовать.Получилось:
обойтись без токенов, сессий и пр.; добавить к ссылке кучу описаний и картинку — в G+ и VK такого функционала нет или он реализуется нетривиально; использовать нативного клиента, если он установлен, но проверку и вызов нужного диалога в отличие от G+ и VK пришлось писать ручками. Не получилось: отследить закрытие пользователем диалогового окна (Cancel), если клиент установлен (для веб-диалога все работает) — в этом случае слушатель (см. код ниже) радостно рапортовал об успешной публикации записи. Поиски решения увенчались сомнительным успехом — если не реализована аутентификация из самого приложения, то в принципе невозможно отследить закрытие окна без публикации записи. Невозможно от слова «совсем». Городить только ради этого обработку авторизации и сессий не хотелось, поэтому пришлось выкручиваться текстом сообщения пользователю. Что в коде…Прежде всего, надо добавить в string.xml айдишник приложения и вписать в манифест метаданные (не забыв разрешить доступ в интернет — это для всех сетей надо!):
private UiLifecycleHelper fbUIHelper;
protected void onCreate (Bundle savedInstanceState) { … fbUIHelper = new UiLifecycleHelper (this, null); fbUIHelper.onCreate (savedInstanceState); }
protected void onActivityResult (int requestCode, int resultCode, Intent data) { … fbUIHelper.onActivityResult (requestCode, resultCode, data, new FacebookDialog.Callback () { //Listener for Facebook-client if installed @Override public void onError (FacebookDialog.PendingCall pendingCall, Exception error, Bundle data) { toastMessage («Запись не опубликована»); }
@Override public void onComplete (FacebookDialog.PendingCall pendingCall, Bundle data) { toastMessage («Если вы сами не отменили команду, то запись опубликована»); } });
protected void onResume () { … fbUIHelper.onResume (); }
protected void onSaveInstanceState (Bundle outState) { … fbUIHelper.onSaveInstanceState (outState); }
protected void onPause () { … fbUIHelper.onPause (); }
protected void onDestroy () { … fbUIHelper.onDestroy (); } Сам код метода, который будет использоваться дочерними классами, выглядит следующим образом: /** * Publish link in FaceBook * @param name — title of block * @param caption — text on bottom of block * @param description — description of link (between title and caption) * @param link — http:// etc * @param pictureLink — http:// etc — link on image in web */ public final void facebookPublish (String name, String caption, String description, String link, String pictureLink) { if (FacebookDialog.canPresentShareDialog (getApplicationContext (), FacebookDialog.ShareDialogFeature.SHARE_DIALOG)) { //Facebook-client is installed FacebookDialog shareDialog = new FacebookDialog.ShareDialogBuilder (this) .setName (name) .setCaption (caption) .setDescription (description) .setLink (link) .setPicture (pictureLink) .build (); fbUIHelper.trackPendingDialogCall (shareDialog.present ()); } else { //Facebook-client is not installed — use web-dialog Bundle params = new Bundle (); params.putString («name», name); params.putString («caption», caption); params.putString («description», description); params.putString («link», link); params.putString («picture», pictureLink); WebDialog feedDialog = new WebDialog.FeedDialogBuilder (this, Utility.getMetadataApplicationId (this), params) .setOnCompleteListener (new OnCompleteListener () { //Listener for web-dialog @Override public void onComplete (Bundle values, FacebookException error) { if ((values!= null) && (values.getString («post_id») != null) && (error == null)) { toastMessage («Запись опубликована»); } else { toastMessage («Запись не опубликована»); }; }; }) .build (); feedDialog.show (); } } ВКонтакте Пожалуй, самая жуткая документация. Пришлось изрядно попотеть. К тому же, полностью избавиться от использования токенов не удалось — без них никак. Но по порядку…Здесь тоже придется залезть в манифест ради одной строки кода:
private String appId = »1234567»; // Need to change to real app_id private static String vkTokenKey = «VK_ACCESS_TOKEN»; private static String[] vkScope = new String[]{VKScope.WALL}; private final VKSdkListener vkSdkListener = new VKSdkListener () { @Override public void onCaptchaError (VKError captchaError) { new VKCaptchaDialog (captchaError).show (); } @Override public void onTokenExpired (VKAccessToken expiredToken) { VKSdk.authorize (vkScope, true, false); } @Override public void onAccessDenied (VKError authorizationError) { new AlertDialog.Builder (SocialNetworkActivity.this) .setMessage (authorizationError.errorMessage) .show (); } @Override public void onReceiveNewToken (VKAccessToken newToken) { newToken.saveTokenToSharedPreferences (getApplicationContext (), vkTokenKey); } }; В методы жизненного цикла Activity эта зараза тоже влезает глубоко, хотя и не так как ФБ: protected void onCreate (Bundle savedInstanceState) { … VKUIHelper.onCreate (this); VKSdk.initialize (vkSdkListener, appId, VKAccessToken.tokenFromSharedPreferences (this, vkTokenKey)); }
protected void onActivityResult (int requestCode, int resultCode, Intent data) { … VKUIHelper.onActivityResult (this, requestCode, resultCode, data); }
protected void onResume () { … VKUIHelper.onResume (this); }
protected void onDestroy () { … VKUIHelper.onDestroy (this); } Однако при публикации ссылки придется заставить пользователя сначала авторизоваться, а потом уже повторно жать на кнопку публикации. Это плохо с точки зрения юзабилити, но другого варианта я не нашла. Выданный один раз токен вроде как действует около часа. Геморно, но не хуже регистрации при каждом постинге, как в веб-диалоге ФБ.Еще одна засада скрывалась там, где я подставы не ожидала ну никак. При отладке из Эклипса на реальном устройстве все работало как часы. Но стоило мне установить то же самое приложение из Гугл Плея, как VK-клиент при постинге ссылки начал ругаться:
{«error»: «invalid_request», «error_description»: «sdk_fingerprint is incorrect»}
При этом авторизация проходит успешно, да и то же самое приложение без VK-клиента через веб работает прекрасно. Т.е. fingerprint корректный. Подозреваю глюк клиента. Или не глюк, а намеренно закрытая возможность, что вполне вероятно, глядя на вот эту ссылку (ближе к концу). Буду разбираться дальше, но если кто в курсе и может помочь — буду признательна.
Еще один нерешенный вопрос — почему заголовок ссылки, видимый в окне предпросмотра, теряется при публикации. Ответа не нашла, увы. Тоже буду признательна за подсказки.
Сам код постинга:
/** * Publish link in Vkontakte * @param message — message about link (may be changed or deleted by user) * @param link — http:// etc * @param linkName — title of link — not published (don’t know why…) */ public final void vkontaktePublish (String message, String link, String linkName) { VKAccessToken token = VKAccessToken.tokenFromSharedPreferences (this, vkTokenKey); if ((token == null) || token.isExpired ()) { VKSdk.authorize (vkScope, true, false); toastMessage («Требуется авторизация. После нее повторите попытку публикации»); } else { new VKShareDialog () .setText (message) .setAttachmentLink (linkName, link) .setShareDialogListener (new VKShareDialog.VKShareDialogListener () { @Override public void onVkShareComplete (int postId) { toastMessage («Запись опубликована»); } @Override public void onVkShareCancel () { toastMessage («Запись не опубликована»); } }).show (getSupportFragmentManager (), «VK_SHARE_DIALOG»); } } Я писала приложение под Support Library, поэтому в коде используется getSupportFragmentManager. Для версий 3.0+ надо заменить его на вызов родного метода.Использование кода Ну, вот, вкратце и все. Теперь наследуем нужную Activity от этой и где требуется используем вызовы хххPublish (). Можно на кнопки вешать, а можно и на popup-меню в ActionBar (правда, в этом случае вызов будет не очень красивый, зато само меню рабочее): public boolean onOptionsItemSelected (MenuItem item) { switch (item.getItemId ()) { case R.id.action_share: View view = findViewById (R.id.action_share); showPopupMenu (view, «https://play.google.com/store/apps/details? id=ru.fantaversum.taleidoscope», «Талейдоскоп — бесплатная библиотека рассказов», «Талейдоскоп на Google Play», «Рассказы и повести с авторскими байками — все бесплатно и без рекламы. Тексты отобраны издательствами. Регулярные обновления.», «http://www.taleidoscope.ru/images/fb_logo.png», «Рекомендую: Талейдоскоп — бесплатная библиотека. Рассказы и повести с авторскими байками — все бесплатно и без рекламы. Тексты отобраны издательствами. Регулярные обновления.», «Талейдоскоп на Google Play»); break; } return super.onOptionsItemSelected (item); } public void showPopupMenu (View view, final String link, final String fb_name, final String fb_caption, final String fb_description, final String fb_pictureLink, final String message, final String linkName) { PopupMenu popup = new PopupMenu (this, view); popup.getMenuInflater ().inflate (R.menu.popup, popup.getMenu ()); popup.setOnMenuItemClickListener (new PopupMenu.OnMenuItemClickListener () { @Override public boolean onMenuItemClick (MenuItem menuItem) { switch (menuItem.getItemId ()) { case R.id.menu_facebook: facebookPublish (fb_name, fb_caption, fb_description, link, fb_pictureLink); return true; case R.id.menu_vkontakte: vkontaktePublish (message, link, linkName); return true; case R.id.menu_googleplus: googleplusPublish (message, link); return true; } return false; } }); popup.show (); } Исходный код всей Activity можно скачать здесь.