Android StudioとFabricでTweetする


以下の環境で、Tweetするプログラムです。2016年7月の情報です。

* Mac OS X 10.11.5 (あんまり関係ないかも）
* JDK 1.8
* Android Studio 2.1.2
* Fabric （これはバージョンはどうやって分かるのか？）

# Fabric

twitterをいじるならtwitter4jだと思っていたのですが、ネットで少し調べるとFabricというキーワードの方が沢山出てくる。しかも、Twitterで開発されているツールらしいので、取り敢えず使ってみる。

作業の流れは、だいたい以下のページの通り。バージョンが少し違うので、多少違うところもあるけれど、困るほどではない。

[Android Twitter Login and Twitter Post Example]
(https://examples.javacodegeeks.com/android/android-twitter-login-twitter-post-example/)



## 登録
[fabricのページ](https://get.fabric.io/) に行って登録する。メールアドレスに確認用のメールが来るので、そのURLで登録完了。どのプラットフォームで開発するのか尋ねられるので、Androidを選択する。（ここの選択は、その後に表示されるドキュメントが変わるだけと思われる。）

## Twitterの開発者の登録
[Twitter Developers](https://dev.twitter.com)で、開発者および開発アプリの登録をしておく。ここでしなくても、Fabricのインストール中に登録画面が出てくるので、そこでやっても同じ。
なお、アプリはTweetもするので、Read & Write になっている必要があります。デフォルトは Read なので、そのままにしておくと、POSTした時に 401 Authorization Required のエラーになります。

![Kobito.XQvbMV.png](https://qiita-image-store.s3.amazonaws.com/0/133150/c67ef6a8-77e4-93c7-5ecf-40357355a2bf.png "Kobito.XQvbMV.png")

あと、Callback URL は何でも良いので入れておくこと、Allow this application to be used to Sign in with Twitter にチェックを入れておくこと。

## Android Studioへのインストール
* Android Studio の Preferences を選ぶ。
* Plugins を選ぶ。
* Browse Repositories... を選ぶ。
* Fabric for Android Studio を選び、install を押す。（ v2.3.1 でした）
* restart するかと聞かれるので、restartする。
* Configure で Plugins を見て、Fabric for Android Studio が入っていればOK

## アプリの作り方
* Start a new Android Studio project を選ぶ。
* 適当な名前をつける（AndroidTwitterExample とした）
* 適当なフォームを選択（Phone and Tablet とした）
![Kobito.l3RrSm.png](https://qiita-image-store.s3.amazonaws.com/0/133150/eab0971e-9949-10aa-0437-530dfbed14b9.png "Kobito.l3RrSm.png")


* 適当な Activity を選択（Empty Activity とした）
![Kobito.Z6beq9.png](https://qiita-image-store.s3.amazonaws.com/0/133150/450f8ed8-461c-4d09-f9b3-060f3e434d59.png "Kobito.Z6beq9.png")


* Activity の名前を決める（デフォルトのままとした）![Kobito.kB1ZmT.png](https://qiita-image-store.s3.amazonaws.com/0/133150/b0ea5dda-38cf-c350-24d2-2884bfcbeb97.png "Kobito.kB1ZmT.png")


* 最初の画面が出たら、右上のFabricのボタン![Kobito.gqUjoq.png](https://qiita-image-store.s3.amazonaws.com/0/133150/28b077ee-0c65-19aa-a09c-c75ce7b5b118.png "Kobito.gqUjoq.png")を押す

![Kobito.LUfGzv.png](https://qiita-image-store.s3.amazonaws.com/0/133150/82e9604b-cb15-964f-df0f-c5b6087a9a35.png "Kobito.LUfGzv.png")


* Fabticの画面が出るので、真ん中のボタンを押してログインする。
 ![Kobito.WUgFHp.png](https://qiita-image-store.s3.amazonaws.com/0/133150/5690778a-f63b-9161-3964-2d0ae2e3f9c2.png "Kobito.WUgFHp.png")


* Organization を選んで Next
* Twitter を選ぶ
![Kobito.N67iU7.png](https://qiita-image-store.s3.amazonaws.com/0/133150/44b59fa7-50b0-0c29-ea1d-155e39a7cc3a.png "Kobito.N67iU7.png")


* Twitter のライブラリを Install（一番上）
![Kobito.0g58zO.png](https://qiita-image-store.s3.amazonaws.com/0/133150/85356dce-dff0-fc75-6b12-b76109c58dc9.png "Kobito.0g58zO.png")


*  Twitterのアカウントを作る。すでに作ってあるので、一番下
![Kobito.fo7OUE.png](https://qiita-image-store.s3.amazonaws.com/0/133150/257af642-5fb5-c23d-2e57-af7d77efc961.png "Kobito.fo7OUE.png")


* Twitter アカウントのキーとシークレットを入力してNext
 ![Kobito.Oc7vJl.png](https://qiita-image-store.s3.amazonaws.com/0/133150/2fd4d017-2653-00d9-d2c8-a7ddd44eb75d.png "Kobito.Oc7vJl.png")


* Fabricが以下の修正をするよ、という画面。下のApplyボタンを押す。
![Kobito.GUFLc2.png](https://qiita-image-store.s3.amazonaws.com/0/133150/3f50b5cf-a201-d4c0-d3c9-9380059950cd.png "Kobito.GUFLc2.png")


* ここで、build して走らせてみろ、とメッセージが出るので、動かしてみる。エラーは出ない。
* Fabricからloginのコードを見る。Log In with Twitter の Get Codeをクリック
![Kobito.Q0Xrvi.png](https://qiita-image-store.s3.amazonaws.com/0/133150/24618966-e7d1-a549-053e-2972bfac3272.png "Kobito.Q0Xrvi.png")



* ログインボタン追加のコードが表示される。これを、今回は app/res/layout/activity_main.xml に入れれば良い。普通に activity_main.xml を選ぶとDesignの画面になっているので、下のタブでTextを選ぶ。
![Kobito.XFlo35.png](https://qiita-image-store.s3.amazonaws.com/0/133150/cb1ad87f-0756-5be4-1cf7-f0d0ab00f7b1.png "Kobito.XFlo35.png")

```xml:修正前のactivity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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="com.example.tadashi.androidtwitterexample.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
</RelativeLayout>
```



Fabricの画面で、修正部分（色が最初から変わっている）をクリックするとコピーされるので、activiti_main.xml に行ってペーストすれば良い。
![Kobito.xP4BsT.png](https://qiita-image-store.s3.amazonaws.com/0/133150/0c79d90c-6b3e-edf3-adc4-32c7c77dd8b2.png "Kobito.xP4BsT.png")


```xml:修正後のactivity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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="com.example.tadashi.androidtwitterexample.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
    <com.twitter.sdk.android.core.identity.TwitterLoginButton
        android:id="@+id/twitter_login_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"/>

</RelativeLayout>
```

Hello World は要らないけど、とりあえず残してある。


* Nextをクリックすると、ログインボタンのコールバックルーチンのための修正コードが表示される。（下のスクリーンショットは途中で切れているけど、もう少し下まである。）
![Kobito.GrOojX.png](https://qiita-image-store.s3.amazonaws.com/0/133150/fc77e937-389d-a932-3d5d-851feaec9b1f.png "Kobito.GrOojX.png")

**（注意）importは自分で修正しないといけない。**

```java:修正後のMainActivity.java
package com.example.tadashi.androidtwitterexample;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.twitter.sdk.android.Twitter;
import com.twitter.sdk.android.core.*;
import com.twitter.sdk.android.core.identity.*;
import io.fabric.sdk.android.Fabric;

public class MainActivity extends AppCompatActivity {

    // Note: Your consumer key and secret should be obfuscated in your source code before shipping.
    private static final String TWITTER_KEY = "	xxxxxxxxxxxxxxxxxxxxxx";
    private static final String TWITTER_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

    private TwitterLoginButton loginButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TwitterAuthConfig authConfig = new TwitterAuthConfig(TWITTER_KEY, TWITTER_SECRET);
        Fabric.with(this, new Twitter(authConfig));
        setContentView(R.layout.activity_main);

        loginButton = (TwitterLoginButton) findViewById(R.id.twitter_login_button);
        loginButton.setCallback(new Callback<TwitterSession>() {
            @Override
            public void success(Result<TwitterSession> result) {
                // The TwitterSession is also available through:
                // Twitter.getInstance().core.getSessionManager().getActiveSession()
                TwitterSession session = result.data;
                // TODO: Remove toast and use the TwitterSession's userID
                // with your app's user model
                String msg = "@" + session.getUserName() + " logged in! (#" + session.getUserId() + ")";
                Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
            }
            @Override
            public void failure(TwitterException exception) {
                Log.d("TwitterKit", "Login with Twitter failure", exception);
            }
        });

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // Make sure that the loginButton hears the result from any
        // Activity that it triggered.
        loginButton.onActivityResult(requestCode, resultCode, data);
    }

}

```

TWITTER_KEYとTWITTER_SECRETは修正してあります。

* Next で、「動かしてみろ」と言われるが、これでログインできるようになっている。失敗したら、Twitter側の設定も確認する。

# 投稿

ただ単にtwitter公式アプリの投稿の画面を出すだけであれば、TweetComposer.Builderを呼べば良いらしいが、今回はポストまで全部アプリでやりたいので、

[TwitterのFabric SDKをAndroid Studioで使ってみる](http://kwmt27.net/index.php/2014/11/10/how-to-use-twitter-fabric-sdk-with-android-studio/)

を参考にやってみました。要は、REST APIを呼べば良いということです。投稿するには、Statuses(Tweets)のupdateをすれば良いということです。

[Access Twitter's REST API](https://docs.fabric.io/android/twitter/access-rest-api.html)

公式のドキュメントを見ると、

1. loginする。
2. TwitterApiClientを得る。
3. StatusesServiceを得る。
4. updateを呼ぶ。

という順番です。loginまでは成功しているので、その後ボタンを押したら定形の文章をポストする、という感じで作ります。投稿のソースコードは、先ほどの参考にしたページのを使わせてもらいます。

# とりあえずPOSTのボタンを作る。

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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="com.example.tadashi.androidtwitterexample.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
    <com.twitter.sdk.android.core.identity.TwitterLoginButton
        android:id="@+id/twitter_login_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Post Button"
        android:id="@+id/post_button"
        android:layout_below="@+id/twitter_login_button"
        android:layout_alignStart="@+id/twitter_login_button"
        android:layout_marginTop="30dp"
        android:layout_alignEnd="@+id/twitter_login_button" />

</RelativeLayout>
```

![Kobito.xCyFRY.png](https://qiita-image-store.s3.amazonaws.com/0/133150/553d5d48-2624-4ce4-72f7-d609b20f5bb0.png "Kobito.xCyFRY.png")

* ロジックの追加
updateの引数の数が参考にしたソースより１つ増えてる。

```java:MainActivity.java
package com.example.tadashi.androidtwitterexample;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.format.*;
import android.util.Log;
import android.view.View;
import android.widget.*;
import com.twitter.sdk.android.Twitter;
import com.twitter.sdk.android.core.*;
import com.twitter.sdk.android.core.identity.*;
import com.twitter.sdk.android.core.models.*;
import com.twitter.sdk.android.core.services.*;
import io.fabric.sdk.android.Fabric;
import java.util.*;

public class MainActivity extends AppCompatActivity {

    // Note: Your consumer key and secret should be obfuscated in your source code before shipping.
    private static final String TWITTER_KEY = "	xxxxxxxxxxxxxxxxxxxxxx";
    private static final String TWITTER_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

    private TwitterLoginButton loginButton;
    private Button postButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TwitterAuthConfig authConfig = new TwitterAuthConfig(TWITTER_KEY, TWITTER_SECRET);
        Fabric.with(this, new Twitter(authConfig));
        setContentView(R.layout.activity_main);

        loginButton = (TwitterLoginButton) findViewById(R.id.twitter_login_button);
        loginButton.setCallback(new Callback<TwitterSession>() {
            @Override
            public void success(Result<TwitterSession> result) {
                // The TwitterSession is also available through:
                // Twitter.getInstance().core.getSessionManager().getActiveSession()
                TwitterSession session = result.data;
                // TODO: Remove toast and use the TwitterSession's userID
                // with your app's user model
                String msg = "@" + session.getUserName() + " logged in! (#" + session.getUserId() + ")";
                Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
            }
            @Override
            public void failure(TwitterException exception) {
                Log.d("TwitterKit", "Login with Twitter failure", exception);
            }
        });

        postButton = (Button) findViewById(R.id.post_button);
        postButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                tweet("test post("
                        +DateFormat.format("yyyy/MM/dd kk:mm:ss", Calendar.getInstance()).toString()
                        +")");

            }
        });

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // Make sure that the loginButton hears the result from any
        // Activity that it triggered.
        loginButton.onActivityResult(requestCode, resultCode, data);
    }


    private void tweet(String message) {
        TwitterApiClient twitterApiClient = TwitterCore.getInstance().getApiClient();
        StatusesService statusesService = twitterApiClient.getStatusesService();

        statusesService.update(message, null, false, null, null, null, false, null, null, new Callback<Tweet>() {
            @Override
            public void success(Result<Tweet> result) {
                Toast.makeText(getApplicationContext(), "post success", Toast.LENGTH_LONG).show();
            }

            public void failure(TwitterException exception) {
                Toast.makeText(getApplicationContext(), "post fail", Toast.LENGTH_LONG).show();
            }
        });
    }

}

```

時刻をつけているのは、どうも全く同じ文字列だと続けてTweetした時に失敗するらしかったからです。

# ポスト成功

![Kobito.KJjwZ0.png](https://qiita-image-store.s3.amazonaws.com/0/133150/c1eca038-00d0-9c1e-9158-886c5e125dac.png "Kobito.KJjwZ0.png")

* エラー処理とか、ログインしているかどうかの確認とかはしていません。