Сегодня по пальцам можно пересчитать мобильные приложения, которые не использовали бы хотя бы ссылки на Twitter или Facebook, я не говорю уже о кнопочке «Расшарить» или лайках. И казалось бы, что по причине такой распростроненности этого функционала, добавление его в приложение не должно вызывать никаких трудностей и не должно занимать много времени. Ну тут стоит отметить, что ребята из Facebook’а молодцы, они об этом позаботились для своего продукта (есть отличный официальный SDK для различных технологий, удобные туториалы и примеры, все хорошо документировано). Чего не скажешь о твиттере! Как дошло дело до шаринга на твиттер – начались серьезные проблемы. Официального SDK, как на Facebook, для твиттера вы не найдете (речь идет, конечно, об SDK под Android). Существует ряд SDK-шек, написанных народными умельцами, но их еще научиться использовать нужно. Конечно, в интернетах есть огромное количество примеров, как добавить желаемый функционал Twitter’а, но после использования одного за одним, я приходил к печальному исходу.. Как правило, оказывалось, что найденные туториалы содержат устаревшую информацию, устаревшие скрины(ибо твиттер обновил интерфейс сервиса). Готовые примеры, которые я скачивал (а их было немало, я скажу, и все они безоговорочно были подписаны как 100% рабочие), безнадежно не работали. В общем, я нормально так повис на этой задаче, что имело неприятные последствия. По этой причине, дабы облегчить жизнь Android разработчикам, хочу показать одно из решений этой неприятной проблемы.

Регистрация приложения

Идем на https://dev.twitter.com и регистрируемся там. Ищем кнопку “Create a new application”.

При создании нового приложения я Вам настоятельно советую заполнять все поля (на всякий случай ☺).

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

Поэтому обязательно заполняйте Callback URL!

Когда вы введете эту иформацию, примете соглашение и правильно введете капчу – создастся приложение. Вы автоматически попадете на его вкладку Details. Там я Вам сразу советую обратить внимание на секцию OAuth settings, а именно на пункт Access level. Если Вам необходимо постить сообщения на твиттер, то уровень доступа (который по умолчанию – Read-only) придется сменить. Для этого переходим на вкладку Settings и находим секцию Application Type, далее выбираем нужный Вам уровень доступа.

Все. Остальное здесь по своему усмотрению.

Далее переходим на вкладку OAuth tool:

Здесь менять ничего не нужно. Главное за чем мы сюда заглянули — Consumer key и Consumer secret. Обязательно сохраните эти ключи!

Далее идем на вкладку @Anywhere domains! Обязательно сюда заглянуть! Ибо вследствии того, что я пропустил эту часть, поначалу были проблемы с авторизацией. Здесь я просто ввел название package, того, что в манифесте Вашего приложения указан. Вводим и жмем Authorize.

Все. Первый этап закончен. Мы создали приложение для твиттера. Идем дальше.

Имплементация

В наших целях будем исползовать Twitter4j SDK. Найти его можно здесь: http://twitter4j.org/en/index.html. Вот тут прямая ссылка: twitter4j-android-2.2.5.zip (slimmed version for Android platform) – заходим и качаем. После распаковки найдете много интересного. Но в данном случае нас интересует папка lib внутри архива, а именно jar файл twitter4j-core-android-2.2.5. Добавляем его в проект. Также еще понадобятся библиотеки для авторизации: http://code.google.com/p/oauth-signpost/downloads/list, а именно signpost-core-1.2.1.1.jar и signpost-commonshttp4-1.2.1.1.jar. Добавляем их в проект.

Продолжим.

Для удобства давайте создадим класс с необходимыми константами:


public class TwitterConstants {
public static final String CONSUMER_KEY = "YourConsumerKeyString";
public static final String CONSUMER_SECRET= "YourLongLongConsumerSecretKey";
public static final String REQUEST_URL = "https://api.twitter.com/oauth/request_token";
public static final String ACCESS_URL = "https://api.twitter.com/oauth/access_token";
public static final String AUTHORIZE_URL = "https://api.twitter.com/oauth/authorize";
public static final String OAUTH_CALLBACK_SCHEME = "appfortwitter";
public static final String OAUTH_CALLBACK_HOST = "callback";
public static final String OAUTH_CALLBACK_URL = OAUTH_CALLBACK_SCHEME + "://" + AUTH_CALLBACK_HOST;
}

Теперь по-порядку: ConsumerKey и ConsumerSecret подставляем строки, сохраненные при создании твиттер приложения на сайте. Урлы – необходимы для авторизации приложения.

Дальше ВНИМАНИЕ! Для того, чтобы после процесса авторизации приложения(а процесс этот будет проходить в браузере!) произошло обратное перенаправление к нашему приложению, необходимо создать специальный Intent Filter для активити, на которое должен будет вернуться пользователь после авторизации. Поэтому в манифесте добавляем следующие строки:


<activity
android:label="@string/app_name"
android:name=".YourActivity"
<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="appfortwitter" android:host="callback" />
</intent-filter>
</activity>

Эта строка:

<category android:name="android.intent.category.BROWSABLE" /> 

говорит о том, что наше активити может быть безопасно запускаться из браузера при правильно сформированном интенте, а именно данных, передаваемых в нем(в данном случае наше активити будет отвечать на интент с урлом вида appfortwitter://callback…).

Замечание! Если в этом случае будут какие-то проблемы – попробуйте добавить в активити следующий атрибут:

android:launchMode="singleInstance”

.

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

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


public class TwitterUtils {
public static Twitter isAuthenticated(SharedPreferences prefs) {
String token = prefs.getString(OAuth.OAUTH_TOKEN, "");
String secret = prefs.getString(OAuth.OAUTH_TOKEN_SECRET, "");
if (token == null || token.length() == 0 || secret == null
|| secret.length() == 0)
return null;
try {
AccessToken a = new AccessToken(token, secret);
Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(TwitterConstants.CONSUMER_KEY,
TwitterConstants.CONSUMER_SECRET);
twitter.setOAuthAccessToken(a);
return twitter;
} catch (IllegalArgumentException e) {
return null;
}
}
public static void saveAccessToken(SharedPreferences prefs, AccessToken a) {
final Editor edit = prefs.edit();
edit.putString(OAuth.OAUTH_TOKEN, a.getToken());
edit.putString(OAuth.OAUTH_TOKEN_SECRET, a.getTokenSecret());
edit.commit();
}
}

После трепетной подготовки перейдем к основным действиям. Важной особенностью используемой библиотеки является то, что все запросы, отправляемые на сервис должны! выполняться в отдельном потоке. В архиве есть еще jar файл twitter4j-async-android-2.2.5, из названия которого можно делать какие-то конкретные предположения, однако я не использовал его, поэтому здесь придется ограничиться тем, что есть.

Итак, класс для запроса на получение Access Token’ов. При нормальной отработке – вызывается диалог авторизации приложения:


public class OAuthRequestTokenTask extends AsyncTask<Void, Void, Void> {
final String TAG = getClass().getName();
private YourActivity context;
private OAuthProvider provider;
private OAuthConsumer consumer;
private Handler errorHandler;
private String url;
public OAuthRequestTokenTask(Context context, OAuthConsumer consumer, OAuthProvider provider, Handler errorHandler) {
this.context = (YourActivity) context;
this.consumer = consumer;
this.provider = provider;
this.errorHandler = errorHandler;
}
@Override
protected Void doInBackground(Void... params) {
try {
Log.i(TAG, "Retrieving request token");

url = provider.retrieveRequestToken(consumer,
TwitterConstants.OAUTH_CALLBACK_URL);//запрос
//на получение Access Token’ов
} catch (Exception e) {
Log.e(TAG, "Error during OAUth retrieve request token", e);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (!TextUtils.isEmpty(url)) {
context.showTwitterDialog(url); //диалог авторизации
//приложения. После того, как отработает – будет сгенерирован
//интент, который вернет пользователя к нужному активити
}
}
}

Вообще можно не использовать диалог для авторизации, а просто направить пользователя в браузер:

startActivity(new Intent(Intent.ACTION_VIEW, url)) 

Если тоже хочется красивенький диалог, придется немного подождать ;)

Класс, для извлечения Access Token’ов – собственно того, без чего авторизация у вас не получится.


public class RetrieveAccessTokenTask extends AsyncTask<Uri, Void, Void> {
final String TAG = getClass().getName();
public static final int RETRIEVAL_SUCCESS = 1;
public static final int RETRIEVAL_FAIL = 2;
private Context context;
private OAuthProvider provider;
private OAuthConsumer consumer;
private Handler handler;
public RetrieveAccessTokenTask(Context context, OAuthConsumer consumer,
OAuthProvider provider, Handler handler) {
this.context = context;
this.consumer = consumer;
this.provider = provider;
this.handler = handler;
}
@Override
protected Void doInBackground(Uri...params) {
final Uri uri = params[0];

final String oauth_verifier =
uri.getQueryParameter(oauth.signpost.OAuth.OAUTH_VERIFIER);

try {
provider.retrieveAccessToken(consumer, oauth_verifier);
//достаем Access Token’ы. Они будут лежать в переменной consumer.

SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(context);
AccessToken a = new AccessToken(consumer.getToken(),
consumer.getTokenSecret());
TwitterUtils.saveAccessToken(prefs, a);
//сохраняем Access Token’ы
Log.i(TAG, "OAuth - Access Token Retrieved");
// здесь сделано не совсем красиво – в Вашем активити объявлено поле
//twitter как статическое. Однако это упрощает жизнь ☺. Вам ничего не мешает //переделать этот момент, т.к. на этом этапе просто инициализируется объект //класса Twitter.
YourActivity.twitter =
new TwitterFactory().getInstance();
YourActivity.twitter.setOAuthConsumer(
TwitterConstants.CONSUMER_KEY, TwitterConstants.CONSUMER_SECRET);
YourActivity.twitter.setOAuthAccessToken(a);
Log.d(TAG, "Twitter Initialised");

if (handler != null) {
Message msg = new Message();
msg.arg1 = RETRIEVAL_SUCCESS;
msg.obj = "OAuth - Access Token Retrieval Success";
handler.sendMessage(msg);
}
} catch (Exception e) {
Log.e(TAG, "OAuth - Access Token Retrieval Error", e);
if (handler != null) {
Message msg = new Message();
msg.arg1 = RETRIEVAL_FAIL;
msg.obj = "OAuth - Access Token Retrieval Error";
handler.sendMessage(msg);
}
}

return null;
}
}

Класс, позволяющий пользователю твитнуть:


public class SendTweetTask extends AsyncTask<Void, Void, Void> {
private String message;
private Handler handler;
public SendTweetTask(String message, Handler handler) {
this.message = message;
this.handler = handler;
}
@Override
protected Void doInBackground(Void... params) {
String result;
//опять же пользуемся статическим полем twitter из Вашего активити
if (YourActivity.twitter != null) {
try {
YourActivity.twitter.updateStatus(message);
result = "Your message has been sent";
} catch (TwitterException e) {
result = "Error sending message";
}
if (handler != null) {
Message msg = new Message();
msg.obj = result;
handler.sendMessage(msg);
}
}
return null;
}
}

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


public class GetTweetsTask extends AsyncTask<Void, Void, Void> {

private static final String TAG = "GetTweetsTask";

private Context context;
private Twitter twitter;
private String queryString;
private List<JSONObject> jsonList = null;

public GetTweetsTask(Context context, Twitter twitter, String queryStr) {
this.context = context;
this.twitter = twitter;
this.queryString = queryStr;
}

@Override
protected Void doInBackground(Void... params) {
try {
QueryResult result = twitter.search(new Query(queryString));
tweets = result.getTweets();
jsonList = convertTimelineToJson(tweets);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (jsonList != null || jsonList.size() != 0) {
//здесь например можно как-то инициализировать Ваш listview.
}
}
/**
* Method that converts a list of Status' to a JSON array that can
* be displayed by the grid view
*
* @param statuses
* @return
*/
private ArrayList<JSONObject> convertTimelineToJson(List<Tweet> tweets) {
ArrayList<JSONObject> JOBS = new ArrayList<JSONObject>();
try {
if (tweets.size()>0){
for (Tweet t : tweets){
String avatar = t.getProfileImageUrl();
JSONObject object = new JSONObject();
object.put("tweet", t.getText());
String timePosted = Utility.getDateDifference(t.getCreatedAt());
object.put("tweetDate", timePosted);
object.put("author", t.getFromUser());
object.put("avatar", avatar);
object.put("userObj", t.getToUser());
object.put("tweetId", t.getId());

JOBS.add(object);
}
}else{
JSONObject object = new JSONObject();
object.put("tweet", "You have not logged in yet");
object.put("author", "");
JOBS.add(object);
}
} catch (JSONException e1) {}
return JOBS;
}
}

В завершающей стадии авторизации, когда мы из браузера возвращаемся к нашему активити, необходимо этот момент обработать. Т.к. когда приходит интент, то у активити срабатывает коллбэк onNewIntent, делать все будем именно здесь – а именно: достанем Access Token’ы:


@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent != null && intent.getData() != null) {
Uri uri = intent.getData();
if (uri != null && uri.toString().startsWith(
TwitterConstants.OAUTH_CALLBACK_URL)) {
new RetrieveAccessTokenTask(this, consumer, provider,
twitterMessage, new Handler()
{
@Override
public void handleMessage(Message msg) {
if (msg != null && msg.obj != null) {
if (msg.arg1 == RetrieveAccessTokenTask.RETRIEVAL_FAIL)
{ //обрабатываем фэйл
} else if (msg.arg1 ==
RetrieveAccessTokenTask.RETRIEVAL_SUCCESS) {
//обрабатываем успех
}
}

}
}).execute(uri);
}
}
}

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


public class TwitterDialog extends Dialog {
private static final FrameLayout.LayoutParams FILL = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT);
private FrameLayout mContent;
private LinearLayout webViewContainer;
private WebView mWebView;
private ImageView mCrossImage;
private ProgressDialog mSpinner;
private String mUrl;
public TwitterDialog(Context context, String url) {
super(context, android.R.style.Theme_Translucent_NoTitleBar);
if (parent.isSmartPhone) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
mUrl = url;
mSpinner = new ProgressDialog(getContext());
mSpinner.requestWindowFeature(Window.FEATURE_NO_TITLE);
mSpinner.setMessage("Loading...");
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.twitter_web_dialog);
mContent = (FrameLayout) findViewById(R.id.twitter_web_view_content);
//можете сами поиграться с LayoutParams, чтобы добиться нужного размера диалога.
Display display = getWindow().getWindowManager()
.getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
LayoutParams lp = new LayoutParams(width * 7 / 8, height * 11 / 14);
mContent.setLayoutParams(lp);
mContent.setPadding(width / 8, height / 14, 0, 0);

webViewContainer = (LinearLayout) findViewById(R.id.twitter_web_view_container);
mCrossImage = (ImageView) findViewById(R.id.twitter_close_web_dialog);
mCrossImage.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
TwitterDialog.this.dismiss();
}
});
int margin = mCrossImage.getDrawable().getIntrinsicWidth() / 2;
mCrossImage.setVisibility(View.INVISIBLE);
setUpWebView(margin);
}
private void setUpWebView(int margin) {
mWebView = new WebView(parent);
mWebView.setVerticalScrollBarEnabled(false);
mWebView.setHorizontalScrollBarEnabled(false);
mWebView.setWebViewClient(new TwitterDialog.TwWebViewClient());
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.loadUrl(mUrl);
mWebView.setLayoutParams(FILL);
webViewContainer.setPadding(margin, margin, margin, margin);
webViewContainer.addView(mWebView);
}
private class TwWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
boolean isDenied = false;
try {
Uri uri = Uri.parse(url);
String param = uri.getQuery();
String name = param.split("=")[0];
if ("denied".equals(name)) {
isDenied = true;
}
} catch (Exception e) {
}
TwitterDialog.this.dismiss();
if (!isDenied) {
getContext().startActivity(
new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
return true;
}
@Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
mSpinner.show();
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mSpinner.dismiss();
/*
* Once webview is fully loaded, set the mContent background to be
* transparent and make visible the 'x' image.
*/
mContent.setBackgroundColor(Color.TRANSPARENT);
mWebView.setVisibility(View.VISIBLE);
mCrossImage.setVisibility(View.VISIBLE);
}
}
}

Пример приложения с исходниками прилагается. Пользуйтесь на здоровье и экономьте свое время☺

Исходники ждет вас тут

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *