본문 바로가기

android/Twitter

Android app에서 Twitter 연동하기 (개선-pincode를 받아와서 인증하자)

http://jeehun.egloos.com/4024992
상단에 원본 링크 입니다. 아래 포스트는 제가 작성한 것이 아니고 egloos.com에 접속되지 않는 분들은 위한 글임을 미리 밝혀 둡니다.
너무 잘 작성된 예제라서 가져옴을 다시 알려드립니다.

안녕하세요 지헌입니다.
원래 녹음기가 끝나면 동적 컨트롤 추가를 해보려고 했는데
막간을 이용해 트위터 연동 앱을 한번 더 수정해보도록 하겠습니다.
이번에는 트위터 인증을 할때 웹브라우져 방식이 아니라
pin 코드를 받아와서 인증하는 방식으로 변경해보겠습니다.
이 방식을 사용할 경우 콜백 URL이 필요 없어지고 앱이 더
깔끔해지는것 같습니다.

소스코드는 바로 전에 올린
Android app에서 Twitter 연동하기 (개선-로그인을 한번만 하자) 에서 작성한 코드를
수정해서 사용하겠습니다. (트위터에 앱 등록절차부터 궁금하신 분은 제가 올린
이전의 트위터 연동 글을 참고하시기 바랍니다.)

먼저 트위터사이트에 들어가서 등록한 앱의 설정을 바꿔야 합니다.
다음처럼 어플리케이션 타입을 Client로 바꿔 줍니다.


다음은 TwitterLogin.java 파일을 수정하겠습니다.
다음이 전체 소스입니다. (패키지명은 좀 틀릴 수 있습니다.
각자 프로젝트를 만드시는 분들에 따라 다들 틀려지겠지요
저는 이번 예제를 만들면서 소스는 이전에 제가 만든걸
거의 그대로 가져다 썼지만 프로젝트는 새로 만들어서
패키지명을 com.android.twittercon3 으로 주었습니다.
프로젝트를 새로 만드셨다면 매니페스트에 인터넷 퍼미션
주는것을 잊으시면 안됩니다.)
************************ TwitterLogin.java ***********************
package com.android.twittercon3;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class TwitterLogin extends Activity
{
// INTENT
Intent mIntent;

public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.twitter_login);
WebView webView = (WebView) findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JavaScriptInterface(), "PINCODE");

// 화면 전환시 WebView에서 화면 전환하도록한다.
// 이렇게하지 않으면 표준 브라우저가 열려 버린다.
webView.setWebViewClient(new WebViewClient()
{
public void onPageFinished(WebView view, String url)
{
// page 렌더링이 완료되면 호출됨.
super.onPageFinished(view, url);
Log.v(C.LOG_TAG, "On Page Finished URL : " + url.toString());
view.loadUrl("javas-ript:window.PINCODE.getPinCode(document.getElementById('oauth_pin').innerHTML);"); // 이글루 편집기의 이상으로 오타가 나고 있습니다. javas-ript 가 아니라 자바스크립트(영어로 쓰셔야되요ㅠㅠ) 입니다.
}
});

mIntent = getIntent();
String url1 = mIntent.getStringExtra("auth_url");

webView.loadUrl(url1);
}

class JavaScriptInterface
{
public void getPinCode(String pin)
{
if (pin.length() > 0)
{
mIntent.putExtra("pin_code", pin);
setResult(RESULT_OK, mIntent);
finish();
}
else
{
Log.v(C.LOG_TAG, "get pin failed...");
}
}
}
}
****************************************************************
위의 코드를 보시면 아시겠지만 웹뷰에서 자바스크립트를 사용가능하게
설정하고 있습니다.
webView.getSettings().setJavaScriptEnabled(true);
그리고 인너 클래스로 자바스크립트와 웹뷰를 연결시켜주는 클래스를
하나 만들어서 웹뷰에 연결시켜주고 있습니다.
webView.addJavascriptInterface(new JavaScriptInterface(), "PINCODE");

그리고 onPageFinished 메서드에서
view.loadUrl("javas-ript:window.PINCODE.getPinCode(document.getElementById('oauth_pin').innerHTML);");
이렇게 호출하는 것으로 getPinCode 가 호출되게 됩니다.
getPinCode는 웹뷰에서 보이는 핀코드 값을 가지고 오게 되고
그와 동시에 인텐트에 그 값을 넣고 액티비티를 끝내고 있습니다.

____________________________________________________________________
참고로 인너클래스에서 바깥쪽(?)클래스의 멤버변수에 접근하고자 할경우
바깥쪽 클래스의 멤버변수는 접근제한자를 주지 않는것이 좋습니다.
그냥 아무런 접근 제한자를 주지 말고(디폴트값) 선언하게 되면
멤버변수가 패키지 범위로 설정되고 이렇게 되면 내부적으로
클래스파일로 변경되는 시점에 불필요한 합성 메서드가 생성되지
않으므로 속도가 더 빨라집니다.
여기서는
Intent mIntent;
위의 변수 선언에서 그렇게 하고 있습니다.
____________________________________________________________________

그러면 이 로그인 액티비티를 부른 TwitterCon3.java 에서 받아서 처리하면 되겠지요

TwitterCon3.java 소스는 다음과 같습니다.
여기서는 onActivityResult 쪽만 수정되었습니다. (아마 맞을겁니다.
소스를 일일히 비교할 수가 없어서리 ㅠㅠ)
TwitterLogin 액티비티로 부터 pin코드를 받아서
그 핀코드를 이용해서 mAccessToken 을 만들고 있습니다.
어차피 만들어 주는것은 이전 방식으로 하던 핀코드를 이용하건
mAccessToken을 만들어야 하는것은 변함이 없으므로
onActivityResult 만 변경점이 있고 나머지는 그대로 사용하면 되겠습니다.
**************************TwitterCon3.java***************************
package com.android.twittercon3;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

import twitter4j.Twitter;
import twitter4j.TwitterFactory;
import twitter4j.conf.Configuration;
import twitter4j.conf.ConfigurationBuilder;
import twitter4j.auth.AccessToken;
import twitter4j.auth.OAuthAuthorization;
import twitter4j.auth.RequestToken;
import twitter4j.media.ImageUpload;
import twitter4j.media.ImageUploadFactory;
import twitter4j.media.MediaProvider;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class TwitterCon3 extends Activity implements View.OnClickListener
{
private Twitter mTwitter;
private RequestToken mRqToken;
private AccessToken mAccessToken;
private Button mBtnLogin, mBtnFeed, mBtnLogout;
private EditText mEtContent;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

mEtContent = (EditText) findViewById(R.id.etContent);

mBtnLogin = (Button) findViewById(R.id.btnLogin);
mBtnFeed = (Button) findViewById(R.id.btnFeed);
mBtnLogout = (Button) findViewById(R.id.btnLogout);

mBtnLogin.setOnClickListener(this);
mBtnFeed.setOnClickListener(this);
mBtnLogout.setOnClickListener(this);
}

@Override
public void onClick(View v)
{
switch(v.getId())
{
case R.id.btnLogin: // Twitter login
login();
break;
case R.id.btnFeed: // Twitter 글쓰기
write();
break;
case R.id.btnLogout: // Twitter logout
logout();
break;
default:
break;
}
}

private void login()
{
try
{
String accessToken = Util.getAppPreferences(this, C.TWITTER_ACCESS_TOKEN);
String accessTokenSecret = Util.getAppPreferences(this, C.TWITTER_ACCESS_TOKEN_SECRET);

if (accessToken != null && !"".equals(accessToken) && accessTokenSecret != null && !"".equals(accessTokenSecret))
{
mAccessToken = new AccessToken(accessToken, accessTokenSecret);

Log.v(C.LOG_TAG, "accessToken : " + mAccessToken.getToken());
Log.v(C.LOG_TAG, "accessTokenSecret : " + mAccessToken.getTokenSecret());
}
else
{
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setDebugEnabled(true);
cb.setOAuthConsumerKey(C.TWITTER_CONSUMER_KEY);
cb.setOAuthConsumerSecret(C.TWITTER_CONSUMER_SECRET);
TwitterFactory factory = new TwitterFactory(cb.build());
mTwitter = factory.getInstance();
mRqToken = mTwitter.getOAuthRequestToken();
Log.v(C.LOG_TAG, "AuthorizationURL >>>>>>>>>>>>>>> " + mRqToken.getAuthorizationURL());

Intent intent = new Intent(this, TwitterLogin.class);
intent.putExtra("auth_url", mRqToken.getAuthorizationURL());
intent.putExtra("request_token", mRqToken.toString());
startActivityForResult(intent, C.TWITTER_LOGIN_CODE);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}

private Configuration getConfiguration(String apiKey)
{
return new ConfigurationBuilder().setMediaProviderAPIKey(apiKey).build();
}

private void write()
{
String path = Environment.getExternalStorageDirectory().getAbsolutePath();
String fileName = "example.jpg";
InputStream is = null;

try
{
if (new File(path + File.separator + fileName).exists())
is = new FileInputStream(path + File.separator + fileName);
else
is = null;

ConfigurationBuilder cb = new ConfigurationBuilder();
String oAuthAccessToken = mAccessToken.getToken();
String oAuthAccessTokenSecret = mAccessToken.getTokenSecret();
String oAuthConsumerKey = C.TWITTER_CONSUMER_KEY;
String oAuthConsumerSecret = C.TWITTER_CONSUMER_SECRET;
cb.setOAuthAccessToken(oAuthAccessToken);
cb.setOAuthAccessTokenSecret(oAuthAccessTokenSecret);
cb.setOAuthConsumerKey(oAuthConsumerKey);
cb.setOAuthConsumerSecret(oAuthConsumerSecret);
Configuration config = cb.build();
OAuthAuthorization auth = new OAuthAuthorization(config);

TwitterFactory tFactory = new TwitterFactory(config);
Twitter twitter = tFactory.getInstance();
ImageUploadFactory iFactory = new ImageUploadFactory(getConfiguration(C.TWITPIC_API_KEY));
ImageUpload upload = iFactory.getInstance(MediaProvider.TWITPIC, auth);

if (is != null)
{
String strResult = upload.upload("example.jpg", is, mEtContent.getText().toString());
twitter.updateStatus(mEtContent.getText().toString() + " " + strResult);
}
else
twitter.updateStatus(mEtContent.getText().toString());
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
try
{
is.close();
}
catch (Exception e)
{}
}
}

private void logout()
{
Intent intent = new Intent(this, TwitterLogin.class);
intent.putExtra("auth_url", C.TWITTER_LOGOUT_URL);
startActivity(intent);
}

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

// 액티비티가 정상적으로 종료되었을 경우
if(resultCode == RESULT_OK)
{
if (requestCode == C.TWITTER_LOGIN_CODE)
{
try
{
Log.v(C.LOG_TAG, "Twitter Pin Code : " + data.getStringExtra("pin_code"));
mAccessToken = mTwitter.getOAuthAccessToken(mRqToken, data.getStringExtra("pin_code"));

Util.setAppPreferences(this, C.TWITTER_ACCESS_TOKEN, mAccessToken.getToken());
Util.setAppPreferences(this, C.TWITTER_ACCESS_TOKEN_SECRET, mAccessToken.getTokenSecret());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
}
*********************************************************************

이렇게 코딩하고 실행하면 트위터 인증이 다음처럼 바뀝니다.

강좌대로 코딩을 하셨다면 위의 창이 잠깐 보였다가 사라지면서 원래 화면으로 돌아갈
것입니다.
그리고 글을 쓰면 글이 올라가는것을 확인 할 수 있습니다. ^^

참고로 이상까지 마친 프로젝트는 다음처럼 구성되어야 합니다.

이상으로 트위터 연동을 마무리 하겠습니다.
도움 되셨다면 리플 하나씩 남겨주시는 센스!!!! ^^

참고로 Util 클래스와 C.java 클래스 소스와
나머지 레이아웃 xml 파일도 올립니다.
****************************** Util.java *********************************
package com.android.twittercon3;

import android.app.Activity;
import android.content.SharedPreferences;

public class Util
{
// 전체 어플에 공용으로 적용되는 SharedPreferences에 Key, Value로 값을 저장한다. Value는 String이다.
public static void setAppPreferences(Activity context, String key, String value)
{
SharedPreferences pref = null;
pref = context.getSharedPreferences(C.LOG_TAG, 0);
SharedPreferences.Editor prefEditor = pref.edit();
prefEditor.putString(key, value);

prefEditor.commit();
}

// 전체 어플에 공용으로 적용되는 SharedPreferences에서 String 값을 가져온다.
public static String getAppPreferences(Activity context, String key)
{
String returnValue = null;

SharedPreferences pref = null;
pref = context.getSharedPreferences(C.LOG_TAG, 0);

returnValue = pref.getString(key, "");

return returnValue;
}
}

************************************************************************

*******************************C.java***********************************
package com.android.twittercon3;

public class C
{
public static final String LOG_TAG = "TwitterCon";
public static final String TWITTER_API_KEY = "트위터 API Key";
public static final String TWITPIC_API_KEY = "트윗픽 API Key";
public static final String TWITTER_CONSUMER_KEY = "트위터 컨슈머 키";
public static final String TWITTER_CONSUMER_SECRET = "트위터 컨슈머 시크릿 키;
public static final String TWITTER_CALLBACK_URL = "http://m.daum.net";
public static final String MOVE_TWITTER_LOGIN = "com.android.twittercon.TWITTER_LOGIN";
public static final int TWITTER_LOGIN_CODE = 10;
public static final String TWITTER_LOGOUT_URL = "http://api.twitter.com/logout";
public static final String TWITTER_ACCESS_TOKEN = "twitter_access_token";
public static final String TWITTER_ACCESS_TOKEN_SECRET = "twitter_access_token_secret";

public static final boolean D = true;
}
************************************************************************

main.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="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp"
>
<EditText
android:id="@+id/etContent"
android:layout_width="fill_parent"
android:layout_height="100dp"
android:padding="5dp"
android:textSize="16sp"
android:gravity="top"
android:typeface="monospace"
android:scrollbars="vertical"
android:background="#FFFFFF"/>

<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:gravity="center_vertical|center_horizontal"
>
<Button
android:id="@+id/btnLogin"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:text="Login"/>
<Button
android:id="@+id/btnFeed"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:text="Write"/>
<Button
android:id="@+id/btnLogout"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:text="Logout"/>
</LinearLayout>

</LinearLayout>
************************************************************************

twitter_login.xml 은 다음과 같습니다.
**************************twitter_login.xml*******************************
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<WebView
android:id="@+id/webView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
/>
</LinearLayout>
************************************************************************

마지막으로 매니페스트 파일입니다.
************************************************************************
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.twittercon3"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".TwitterCon3"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- ACT : Twitter Login -->
<activity android:name=".TwitterLogin" android:label="Twitter Login"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden"
android:configChanges="orientation|keyboardHidden"
android:theme="@android:style/Theme.NoTitleBar">
<intent-filter>
<action android:name="com.android.twittercon.TWITTER_LOGIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
************************************************************************