Fabric from Android で写真を Tweet する。

## Fabric
![introducing-fabric-homepage.png](https://qiita-image-store.s3.amazonaws.com/0/85718/b2197b27-07a2-8b52-73d5-918825253fa8.png "introducing-fabric-homepage.png")
[Fabric.io](https://get.fabric.io)

Fabric は Twiter が提供しているツールで、アプリ開発時にアプリケーションをチームへスキームすることが可能です。
また、クラッシュログやアプリの配布に、Twitter ログインなど様々な機能が備わっています。

### Fabric Kits
Fabric の中にはとても優れている Framework が存在します。

- [crashlytics](https://fabric.io/kits/android/crashlytics) (Crash Reporting)
- [answers](https://fabric.io/kits/android/crashlytics) (Mobile Analytics)
- [DIGITS](https://fabric.io/kits/android/digits) (Mobile Identity)
- [Twitter Kit](https://fabric.io/kits/android/twitterkit) (Growth)
- [mopub](https://fabric.io/kits/android/mopub) (Monetization)

そんな中、今回お伝えするのは、Fabric の機能のでもある、Twitter Kit で画像データーを Tweet する処理を作ったので、その時の覚書にはなりますが、お伝えしたいと思います。

## Twitter Kit

![twitter_kit-2bb33c7ef570f985b3ce40cbef3f04e4.png](https://qiita-image-store.s3.amazonaws.com/0/85718/dad55dbe-fb12-e9cf-c7a2-6b8a4dd6b9c2.png "twitter_kit-2bb33c7ef570f985b3ce40cbef3f04e4.png")

[Twitter Kit](https://fabric.io/kits/android/twitterkit)

### 主な機能
 - Authentication
 - Use OAuth Echo
 - Request a User's Email Address
 - Access Twitter's REST API
 - Show Tweets
 - Show Timelines
 - Compose Tweets
 
 目立った機能はここら辺です。
 それぞれの機能の簡単な説明です。

 
### Authentication
 - Log in with Twitter
	 - 標準のログインボタンが用意され簡単にTwitter Log in が実装できる
	 - ゲストログインが可能で、アカウントのないユーザーでもTwitterの機能の一部分を利用することができる。

### Use OAuth Echo

- OAuth 処理を簡単に実装できる
  - リクエストへ必要な Auth 情報を利用できる。

### Request a User's Email Address

- Email アドレスの要請

### Access Twitter's REST API

- サポートされている Twitter API を簡単に実装できる
- 拡張し標準でサポートされていない Twitter API も作成可能

### Show Tweets

- 公式標準の TweetUI を開発者のアプリケーションに簡単に埋め込むことが可能
- リスト表示にも対応
- ダークモードに対応
- デザインのカスタマイズに対応
- Tweet Action に対応

### Show Timelines
- タイムラインを検索し一覧表示することができる

### Compose Tweets
- Tweet 機能を開発者のアプリケーションに簡単に組み込むことが可能
- メッセージと画像投稿に対応

## Access Twitter’s REST API
今回のメインはこちらになります。
Fabric の機能で REST API が利用可能なので、こちらの機能を使って対応を進めていくことにします。


まず、標準で対応してれば、標準で対応したいと思います。
（対応していれば苦労しなかったんですけどねw）

### 標準対応している API
- Statuses (Tweets)
	- mentionsTimeline
	- userTimeline
	- homeTimeline
	- retweetsOfMe
	- show
	- lookup
	- update
	- retweet
	- unretweet
	- destroy
- Favorites
	- list
	- create
	- destroy
- Search
	- tweets
- Lists
	- statuses
- Collections
	- entries

この一覧が何を示しているかよくわからない人のために！
こちらは、Twitter API の URL Path になり、それぞれ Path ごとで機能が分かれています。
具体的な内容については、[REST APIs](https://dev.twitter.com/rest/public) をご覧ください。

## Statuses (Tweet)
ざっくりとですが、Tweet関連の機能がまとめられている API 群です。

## POST statuses/update_with_media
[POST statuses/update_with_media](https://dev.twitter.com/rest/reference/post/statuses/update_with_media)

###Tweet 投稿と一緒にメディアを投稿するためのAPIになります。

今までのうる覚えで使おうと思っていたのですが、Twitter Kit では標準対応されていません。
また、お気付きの方がいると思いますが、こちらの API は deprecated になっていました。
こちらの API は非推奨なので利用するのはやめましょう。

ということは、別の対応方法が存在します。

## POST media/upload
[POST media/upload](https://dev.twitter.com/rest/reference/post/media/upload)
### Twitter へメディアを登録するAPIになります。
60分間サーバーに保持してくれる？ので、その間にアップロードした画像を Tweet へ反映させなければいけません。


## POST statuses/update
[POST statuses/update](https://dev.twitter.com/rest/reference/post/statuses/update)
### Tweet を投稿するAPIになります。
Tweet する際に MediaID を同梱することで、メディア付きな Tweet を投稿することが可能です。

この二つのAPI を利用することで画像投稿が可能になります。

# 実装編
※実際に実装したソースコードを抜粋しているため、導入する際は、よしなにしてください！

## Twitter Login

Twitter ログインを行う。
Twitter ログインページを表示しアカウント情報を入力する画面が表示される。


```
TwitterCore twitterCore = TwitterCore.getInstance();
twitterCore.logIn(activity, new Callback<TwitterSession>() {
    @Override
    public void success(Result<TwitterSession> result) {
        // ログイン処理に成功
    }

    @Override
    public void failure(TwitterException e) {
        // ログイン処理に失敗
    }
})
        
```

## Twitter Tweet (POST statuses/update)

Twitte Kit 標準で用意されている、Twitter へ Tweet する処理。
※ユーザーログイン処理がが完了していること！

```
TwitterSession twitterSession = TwitterCore.getInstance().getSessionManager().getActiveSession();
StatusesService statusesService = new TwitterApiClient(twitterSession).getStatusesService();
statusesService.update("Tweet Message.", null, false, null, null, null, false, false, new Callback<Tweet>() {
    @Override
    public void success(Result<Tweet> result) {
        // 投稿処理成功
    }

    @Override
    public void failure(TwitterException e) {
        // 投稿処理失敗
    }
});
```
Tweet ですが、こちらの標準だと、メディアを追加することができません。
ということから、こちらの処理を自前実装します。

## StatusesService の拡張

標準で用意されている'StatusesService#update'に'media_ids'が対応していませんでしたので、
再定義し自身のinterfaceクラスを定義します。

Twitter Kit の通信処理では、Square の [Retrofit](http://square.github.io/retrofit/) を利用しています。

```
public interface SKNStatusesService {
...
@FormUrlEncoded
@POST("/1.1/statuses/update.json")
void update(
@Field("status") String status,
@Field("in_reply_to_status_id") Long inReplyToStatusId,
@Field("possibly_sensitive") Boolean possiblySensitive,
@Field("lat") Double lat,
@Field("long") Double longDouble,
@Field("place_id") String placeId,
@Field("display_cooridnates") Boolean displayCooridnates,
@Field("trim_user") Boolean trimUser,
@Field("media_ids") String mediaIds,
Callback<Tweet> cb);
```

## TwitterApiClient　の拡張

実際に先ほど作った、SKNStatusesService.class を生成する処理を実装します。
SKNStatusesServiceを使うときは必ず、このメソッドを利用します。

```
public class SKNTwitterApiClient extends TwitterApiClient {
...
public SKNStatusesService getfalStatusesService() {
    return (SKNLStatusesService)this.getService(SKNStatusesService.class);
}

```

## Twitter Tweet with MediaID (POST statuses/update)
[#extensions](http://docs.fabric.io/android/twitter/access-rest-api.html#extensions)

先ほど作った、SKNStatusesService と SKNTwitterApiClient を使うぐらいで、あとは標準のと変わりありませんね！
案外簡単に拡張することが可能なのがわかります。

```
TwitterSession twitterSession = TwitterCore.getInstance().getSessionManager().getActiveSession();
SKNStatusesService statusesService = new SKNTwitterApiClient(twitterSession).getfalStatusesService();
statusesService.update("Tweet Message.", null, false, null, null, null, false, false, "media_id_string1,media_id_string2,media_id_string3,media_id_string4", new Callback<Tweet>() {
    @Override
    public void success(Result<Tweet> result) {
        // 投稿処理成功
    }

    @Override
    public void failure(TwitterException e) {
        // 投稿処理失敗
    }
});
```
ここまですんなりできたでしょうか？
さて、次に問題になってくるのが、MediaIDの取得です。
Android -> 画像 -> TypeFile -> POST -> Twitter -> MediaID
この流れで実際の画像から MediaID を生成し取得する必要があります。

## 画像を Twitter へ登録する

## POST media/upload
残念ながら Twitter Kit では media/upload API に標準で対応していません。
対応していないのなら作るのが仕事！
さて作りましょう！
手順は先ほど statuses/update で対応した内容に +α します。

## MediaService 作成
今回の API は画像のバイトデーターを送信するので、マルチパートにします。

```
public interface SKNMediaService {
...
@Multipart
@POST("/1.1/media/upload.json")
void upload(@Part("media") TypedFile media, Callback<Media> cb);

```

## API Response Media の作成
media/Upload の Response の値を格納するモデルクラスを作成します。

```
public class Media {
    @SerializedName("media_id")
    public final long mediaId;
    @SerializedName("media_id_string")
    public final String mediaIdString;
    @SerializedName("size")
    public final long size;
    @SerializedName("image")
    public final Media.Image image;
    
    public Media(long mediaId, String mediaIdString, long size, Image image) {
        this.mediaId = mediaId;
        this.mediaIdString = mediaIdString;
        this.size = size;
        this.image = image;
    }

    public static class Image {
        @SerializedName("w")
        public final long width;
        @SerializedName("h")
        public final long height;
        @SerializedName("image_type")
        public final String imageType;
        
        public Image(long width, long height, String imageType) {
            this.width = width;
            this.height = height;
            this.imageType = imageType;
        }
    }
}
```

## FALTwitterApiClient の拡張
media/upload API では、Twitter API の HOST 名を変更する必要があります。
すんなり変更できると思ったのですが、今のところ、私がわかっているのは、こちらの方法です。

```
public class SKNTwitterUploadApiClient {
...
    final ConcurrentHashMap<Class, Object> services;
    final RestAdapter adapter;

    static final String TWITTER_API_HOST_NAME = "https://upload.twitter.com";

    SKNTwitterUploadApiClient(TwitterAuthConfig authConfig, Session session, TwitterApi twitterApi, SSLSocketFactory sslSocketFactory, ExecutorService executorService) {
        if(session == null) {
            throw new IllegalArgumentException("Session must not be null.");
        } else {
            this.services = new ConcurrentHashMap();
            Gson gson = (new GsonBuilder()).registerTypeAdapterFactory(new SafeListAdapter()).registerTypeAdapterFactory(new SafeMapAdapter()).create();
            this.adapter = (new RestAdapter.Builder()).setClient(new AuthenticatedClient(authConfig, session, sslSocketFactory)).setEndpoint(twitterApi.getBaseHostUrl()).setConverter(new GsonConverter(gson)).setExecutors(executorService, new MainThreadExecutor()).build();
        }
    }

    public SKNTwitterUploadApiClient(Session session) {
        this(TwitterCore.getInstance().getAuthConfig(), session, new TwitterApi(TWITTER_API_HOST_NAME), TwitterCore.getInstance().getSSLSocketFactory(), TwitterCore.getInstance().getFabric().getExecutorService());
    }

    public SKNMediaService getMediaService() {
        return (SKNMediaService)this.getService(SKNMediaService.class);
    }

    protected <T> T getService(Class<T> cls) {
        if(!this.services.contains(cls)) {
            this.services.putIfAbsent(cls, this.adapter.create(cls));
        }

        return (T) this.services.get(cls);
    }s
```


## POST media/upload 実装編

※ユーザーログイン処理がが完了していること！

```
File mediaFile ...
TypedFile typedFile = new TypedFile("multipart/form-data", mediaFfile);
...

TwitterSession twitterSession = TwitterCore.getInstance().getSessionManager().getActiveSession();
SKNMediaService mediaService = new SKNTwitterUploadAPIClient(twitterSession).getMediaService();
mediaService.upload(typedFile, new Callback<Media>() {
    @Override
    public void success(Result<Media> result) {
        // メディア登録完了           
    }

    @Override
    public void failure(TwitterException e) {
        // メディア登録失敗
    }
});
```

以上長くなってしまいましたが、こちらが、Fabric で 画像を Tweet する処理になります。

## まとめ
 - Fabric
 - Retrofit
 - POST statuses/update の拡張
 - POST media/upload の作成
 - TwitterUploadApiClient の拡張
 - Media モデルクラスの作成
 - MediaService の作成

わかると意外に簡単ですが、意外に情報が無く結局自分でコードを読むことで解決することができました。
みなさんの参考になればと思います。