前記事で、FacebookSDKを使うための準備ができましたので、いよいよコーディングです。
サンプルについて
今回作ったサンプルは次のような動作をするものとします。
- アプリが起動すると自動的にFacebookにログインします
- テキストを入力してボタンを押すと、本人のタイムラインに投稿します
ログアウトがないとか、その他問題がありそうな箇所はこの記事を参考にして、適当に補ってください。
ログイン機能について
AndroidでFacebookSDKを使ったログイン機能としては、大きく分けて、
UserSettingsFragment クラスを利用したもの
LoginButton ウィジェットを利用したもの
UI部品を使わずに、自分でログイン・ログアウトの制御を行うもの
に分かれると思います(1.と2.は、FacebookSDKが提供するUIを使う形になります)。
それぞれの参考資料としては、
- FacebookSDK に含まれるSessionLoginSample サンプルプロジェクトのLoginUsingLoginFragment
- Facebookのチュートリアル(1 - Authenticate with Facebook Login)やHow To(Use Facebook Login)
- Getting Started with the Facebook SDK for Android(簡単な例が載ってます)や、FacebookSDK に含まれるSessionLoginSample サンプルプロジェクトのLoginUsingActivityActivity
などがあります。
チュートリアルの充実ぶりなどからは、たぶん、LoginButton を使うのが推奨なんだろうけど、アプリへの組み込みを考えて3.のFacebookSDKのUI部品を使わずにログイン・ログアウト機能を実現してみました。
準備
Facebookにアプリを登録時に表示されたApp ID をAndroidManifest.xml に登録する必要があります。下記の2行をAndroidManifest.xml のapplication タグ内に追記します。
<meta-data android:value="@string/app_id" android:name="com.facebook.sdk.ApplicationId"/> <activity android:name="com.facebook.LoginActivity"></activity>
なお、別途文字列として@string/app_id に実際のApp ID を定義しています。
また、インターネットへのアクセスを認めるパーミッションも追記しておきます。
<uses-permission android:name="android.permission.INTERNET"/>
ログイン用のレイアウトは、テキストビューとボタンをひとつだけ持ったものとします。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <Button android:id="@+id/post_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:text="投稿" /> <EditText android:id="@+id/message" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_above="@+id/post_button" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:ems="10" android:gravity="top|left" android:inputType="textMultiLine" /> </RelativeLayout>
Activityの実装
Facebookではログイン・ログアウトの制御はSessionが行います。また、ログイン状態が変更した場合、Session.StatusCallback 経由で通知を受け取ることができます。
全体の流れは、FacebookSDK に含まれるSessionLoginSample サンプルプロジェクトのLoginUsingActivityActivityを元にしています。
あと、Facebookではログイン時に多くのパーミッションを与えるのではなく、ログイン時は最低限必要なパーミッションのみを与えて、アプリを使うなかで必要なパーミッションが出てくれば、再度ユーザーに確認をとる方式を推奨しています。
そして、投稿には、Extended Permissions の publish_actions という書き込み用のパーミッションが必要になります。
このため、投稿ボタン押下時には、現在持っているパーミッションを確認して、不足しているなら再度ユーザーに確認を求めるという処理が必要になります。
したがってFacebookにログインしていない状態であれば、投稿までには2回確認ダイアログが表示されます。なお、Google Play からFacebookアプリをダウンロードしてインストールしている場合とインストールしていない場合で挙動が若干異なります。
これらを考慮して作ったものが、下記のものになります。
package com.example.facebooksample; import java.util.Arrays; import java.util.Date; import java.util.List; import android.app.Activity; import android.app.AlertDialog; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import com.facebook.FacebookAuthorizationException; import com.facebook.FacebookOperationCanceledException; import com.facebook.FacebookRequestError; import com.facebook.Request; import com.facebook.Response; import com.facebook.Session; import com.facebook.Session.OpenRequest; import com.facebook.SessionState; import com.facebook.model.GraphObject; public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private static final List<String> PERMISSIONS = Arrays.asList("publish_actions"); private EditText mEditText; private class SessionStatusCallback implements Session.StatusCallback { @Override public void call(Session session, SessionState state, Exception exception) { //updateView(); Log.d(TAG, "SessionStatusCallback"); onSessionStateChange(session, state, exception); } } private Session.StatusCallback statusCallback = new SessionStatusCallback(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG,"onCreate"); // 投稿用UI設定 mEditText = (EditText) findViewById(R.id.message); Button btn = (Button) findViewById(R.id.post_button); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //doPost(); preparePublish(); } }); // Facebook ログイン管理 Session session = Session.getActiveSession(); if (session == null) { if (savedInstanceState != null) { session = Session.restoreSession(this, null, statusCallback, savedInstanceState); } if (session == null) { session = new Session(this); } Session.setActiveSession(session); if (session.getState().equals(SessionState.CREATED_TOKEN_LOADED)) { //session.openForPublish(getOpenRequest()); session.openForRead(new OpenRequest(this)); } } // ログイン状態の確認 if (! session.isOpened()) { doLogin(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public void onStart() { super.onStart(); Session.getActiveSession().addCallback(statusCallback); Log.d(TAG,"onStart"); } @Override public void onStop() { super.onStop(); Session.getActiveSession().removeCallback(statusCallback); Log.d(TAG,"onStop"); } @Override protected void onResume() { super.onResume(); Session session = Session.getActiveSession(); Log.d(TAG,"onResume:" + "session state is " + session.getState()); if (session.getState().equals(SessionState.CLOSED_LOGIN_FAILED) || session.getState().equals(SessionState.CLOSED)) { Toast.makeText(this, "Facebook認証に失敗しました。", Toast.LENGTH_LONG).show(); finish(); } } @Override protected void onPause() { super.onPause(); Log.d(TAG,"onPause"); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); Log.d(TAG,"onActivityResult"); Session.getActiveSession().onActivityResult(this, requestCode, resultCode, data); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Log.d(TAG,"onSaveInstanceState"); Session session = Session.getActiveSession(); Session.saveSession(session, outState); } private void onSessionStateChange(Session session, SessionState state, Exception exception) { if ((exception instanceof FacebookOperationCanceledException || exception instanceof FacebookAuthorizationException)) { Log.w(TAG, "error occured:" + exception.getMessage()); } else if (state == SessionState.OPENED_TOKEN_UPDATED) { doPost(); } } private void doLogin() { Session session = Session.getActiveSession(); Log.d(TAG,"doLogin: session state is " + session.getState() + ", isOpend:" + session.isOpened() + ", isClosed:" + session.isClosed()); if (!session.isOpened()) { if (session.isClosed()) { session = new Session(this); Session.setActiveSession(session); } //session.openForPublish(getOpenRequest()); session.openForRead(new OpenRequest(this)); } else { Session.openActiveSession(this, true, statusCallback); } } private boolean hasPublishPermission() { Session session = Session.getActiveSession(); return session != null && session.getPermissions().contains("publish_actions"); } private void preparePublish() { Session session = Session.getActiveSession(); if (session != null) { if (hasPublishPermission()) { // We can do the action right away. doPost(); } else { // We need to get new permissions, then complete the action when we get called back. session.requestNewPublishPermissions(new Session.NewPermissionsRequest(this, PERMISSIONS)); } } } private void doPost() { Log.d(TAG,"doPost"); final String message = "投稿テストです:" + (new Date().toString()) + "n" + mEditText.getText().toString(); Request request = Request .newStatusUpdateRequest(Session.getActiveSession(), message, new Request.Callback() { @Override public void onCompleted(Response response) { showPublishResult(message, response.getGraphObject(), response.getError()); } }); request.executeAsync(); } private interface GraphObjectWithId extends GraphObject { String getId(); } private void showPublishResult(String message, GraphObject result, FacebookRequestError error) { Log.d(TAG,"showPublishResult"); String title = null; String alertMessage = null; if (error == null) { title = "ステータスアップデート成功"; String id = result.cast(GraphObjectWithId.class).getId(); alertMessage = "ID=" + id; } else { title = "ステータスアップデート失敗"; alertMessage = error.getErrorMessage(); } new AlertDialog.Builder(this) .setTitle(title) .setMessage(alertMessage) .setPositiveButton("OK", null) .show(); } }
投稿は、Request#newStatusUpdateRequestメソッドを利用して行っています。
ここまでできたら、アプリを実行すると、Facebookへのログインが確認され、問題なく投稿できることがわかります。
(参考)
- パーミッションの追加処理については、たとえば、Publish to Feed Step2あたりを参考にしてください
- パーミッションそのものについては、Permissions などを参考にしてください
おまけ:タイムラインへ画像つきで投稿
画像つきで投稿する場合は、Request#newUploadPhotoRequest メソッドを使えばできます。
しかし、メソッドの引数を見ると、テキストメッセージを与える部分がありません。調べてみると、これは、requestオブジェクトのパラメータを使う必要があるそうです。
たとえば、このようにします(画像は、mImageに入っているとします)。
final String message = mEditText.getText().toString(); Request request = Request.newUploadPhotoRequest(Session.getActiveSession(), mImage, new Request.Callback() { @Override public void onCompleted(Response response) { // 必要があれば投稿完了時の処理 FacebookPostActivity.this.finish(); } }); Bundle params = request.getParameters(); params.putString("message", message); request.executeAsync();
これで、メッセージ+画像で投稿できます。
(参考)
Post pic on wall with message with Android Facebook SDK 3.0 - Stack Overflow