21
17

More than 3 years have passed since last update.

【初心者向け】Android×KotlinでTwitter4Jを使ってみる

Last updated at Posted at 2019-12-10

はじめに

この記事は初心者の我々でもかけるAndroid アドベントカレンダーの11日目の記事です。
こういったものを書くのは初めての初学者ですが、自分と同じようにアプリ開発でTwitterを使ってみたい初心者の方が、この記事を読んで少しでも役に立てていただければ幸いです。

何か本文中で誤りがありましたら、ぜひコメント等でご指摘くださいm(_ _)m

この記事の内容

【対象者】
・Android開発でTwitterAPIを使ってみたい人
・Twitter4Jで色々調べてるけど、日本語文献でKotlinのコードが全然なくて困っている人
・よくわからないけれど、非同期処理でコルーチンとやらを使ってみたい人

※APIとは?等のことは説明しないので、各自でお調べください。

【やってみること】
・Twitter4Jの基本的な使い方を学んでみる
・ベアラートークンを使ってツイートを取得してみる
・アクセストークンを使ってツイートを取得・投稿してみる
・Coroutines(コルーチン)についてちょっとだけ勉強してみる

※エラー処理は一切していません。これも読了後に各自でお調べいただければと思います。

筆者の環境は以下の通りです
・Android Studio(Version 3.5.1)
・Kotlin (Version 1.3.50-release-Studio3.5-1)
・Twitter4J (Version 4.0.7)
・Kotlin coroutines用ライブラリ(Version 1.3.2)

前準備:Twitter API キーの取得

まずはTwitterでDeveloperアカウント登録をする必要があります。やり方についてですが、以下の方がとても詳細に流れを書いてくださっているので、ぜひそちらをご参考ください。

Twitter API 登録 (アカウント申請方法) から承認されるまでの手順まとめ ※2019年8月時点の情報

私は2019年10月ごろに登録しましたが、2か月しか経っていないのでほとんど内容は同じでした。英語で記述しなければならないところはGoogle翻訳を使えば何とかなります。最近のGoogle翻訳はすごいですね!!

上記リンクの内容に補足で、もしDMを送りたいとかの場合はメニューの中のPermissionsタブを開いて権限を変更してください。
permission_fix.PNG
なお、APIキーの取得の中で「Consumer Key(APIキー)」「Consumer Secret(APIシークレットキー)」「Access Token」「Access Token Secret」の4種類が出てきます。
前者2つはTwitterAPIを使うのに必ず必要で、後者2つは投稿する系の使い方をする時に必要になります。
このアクセストークンはユーザーごとに固有で、「Twitterクライアントを自作する」みたいなことをする場合はそれを最初に取得する処理が必要になってきます。

(登録の中でもらえたアクセストークンは自分のアカウント専用のものです。例えばこれを使ってツイートすると、登録に使ったアカウントでツイートすることになります。)

Twitter4Jを導入する

TwitterAPIを使って何かする場合は、認証やらJSONやら色んな事を考えなければいけないのですが、それを全部やってくれるという神様みたいなライブラリがあります。それがTwitter4Jです!製作者様に圧倒的感謝…!

では、さっそくプロジェクトに導入していきましょう。
Gradleに以下の文を書き加えてSyncしてください。調べてみると「.jarをダウンロードしなきゃいけない」とか書いてあるものもありますが、この一文を加えるだけで大丈夫です。
また、後ほど非同期処理にコルーチンを用いるので、それも一緒に追加しておきます。

app/build.gradle
dependencies{
        //省略
        implementation 'org.twitter4j:twitter4j-core:4.0.7'
        implementation"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
        implementation"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
        //省略
}

これでTwitter4Jの導入は終わりです。簡単ですね!
実際に使う際にはインターネット通信を行うので、マニフェストに許可する旨を書いておきましょう。

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest>
    <application>
        <activity>
            <!-- その他省略 -->
        </activity>
    </application>
    <uses-permission android:name="android.permission.INTERNET"/>
</manifest>

実際にツイートしてみる

それでは実際にツイートを投稿してみましょう。そのためには、まず先ほど入手した各種キーをセットする必要があります。セットする方法は「プロパティファイルを使う方法」「プログラムから直接設定する方法」があるので、ここでは両方紹介しておきます。

プロパティファイルを使う方法

まず左側のプロジェクトツールウィンドウがProjectビューになっているのを確認して、app/src/mainで右クリック、New→Directoryを選択してresourcesというフォルダを作成します。
次にそのresources上で右クリックしてNew→Fileを選択、名前をtwitter4j.propertiesとして作成しましょう。
最終的にAndroidビューでこのようになっていれば大丈夫です。

フォルダ構成.PNG

そうしたら、twitter4j.propertiesを開いて中身を次のように書いてください。

twitter4j.properties
debug=true
oauth.consumerKey=XXXXXXXXXXXXXXXX
oauth.consumerSecret=XXXXXXXXXXXXXXXXXX
oauth.accessToken=XXXXXXXXXXXXXXXX
oauth.accessTokenSecret=XXXXXXXXXXXXXXXX
#それぞれ右側に各種キーをコピペする

以上で設定は完了です。それでは試しにツイートしてみましょう。いったん何も考えず、次のようにコードを打ち込んでみてください。
また、MainActivityのレイアウトファイルはTextViewとButtonを1つずつ置いたものを用意しておきましょう。

(ここの処理はもっと良い書き方がありますが、ここではlaunchを使いたい&初心者(=自分)が流れとして見やすいようにこうしています。)

MainActivity.kt

//プロパティファイルを使う場合
class MainActivity : AppCompatActivity(), CoroutineScope {

    //Coroutinesを扱うための設定(詳細は後述)
    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.setOnClickListener {                  //ID:buttonのボタンをクリックした際の処理
            onClick()
        }
    }

    private fun onClick() {
        launch {
            textview.text = "Now Sending"            //ここはメインスレッドで動作するのでViewの変更ができる

            async(context = Dispatchers.IO) {
                val twitter = TwitterFactory().getInstance()
                twitter.updateStatus("Test tweet from Twitter4J #twitter4j")      //ツイートの投稿
            }.await()                                //.await()で通信処理が終わるまで待機

            textview.text = "finish"
        }
    }

    override fun onDestroy() {
        job.cancel()                                 //すべてのコルーチンキャンセル用
        super.onDestroy()
    }
}

実行してボタンを押すと、TextViewの表示が変わってツイートされるはずです。実際に確認してみましょう。

post1_fix2.PNG

ツイートが投稿されました!

プログラムから直接設定する方法

この方法はプロパティファイルを作成する必要がありません。先ほど書いたコードの中のonClickを少し変更します。変更後、同様に実行してみてください。(プロパティファイルは消しておいてください。)

MainActivity.kt
//プログラムから直接設定する場合
    private fun onClick() {
        launch {
            textview.text = "Now Sending"

            async(context = Dispatchers.IO) {
                val cb = ConfigurationBuilder()
                cb.setDebugEnabled(true)
                    .setOAuthConsumerKey("XXXXXXXXXXXXXXXX")
                    .setOAuthConsumerSecret("XXXXXXXXXXXXXXXX")
                    .setOAuthAccessToken("XXXXXXXXXXXXXXXX")
                    .setOAuthAccessTokenSecret("XXXXXXXXXXXXXXXX")                //各種キーの設定

                val tf = TwitterFactory(cb.build())
                val twitter = tf.getInstance()
                twitter.updateStatus("Test2 tweet from Twitter4J  #twitter4j")    //ツイートの投稿
            }.await()

            textview.text = "finish"
        }
    }

こちらも同様に投稿できていますね!

ベアラートークンを使ってツイートを取得してみる

先ほどは自分で取得したアクセストークンを使ったため投稿ができていましたが、本来はユーザーごとに認証画面を表示し、アクセストークンを入手しなければなりません。
ただし、「ハッシュタグを検索する」「ある人のツイートだけを見てみる」といった「投稿しない系の処理」だったら、アクセストークン無しに行うことができます。
それを実現するのがベアラートークン(Bearer Token)です。

まず、先ほど作成したtwitter4j.propertiesの中身を次のように書き換えてください。

twitter4j.properties
debug=true
enableApplicationOnlyAuth=true
http.useSSL=true
oauth.consumerKey=XXXXXXXXXXXXXXXX
oauth.consumerSecret=XXXXXXXXXXXXXXXXXX

そうしたらMainActivity.ktの中のonClickを次のように書き換えます。

MainActivity.kt
//Bearer Tokenでの処理
//自分のツイートを検索してログに出力

    private fun onClick() {
        launch {
            textview.text = "Now Loading"

            async(context = Dispatchers.IO) {
                val twitter =  TwitterFactory().getInstance()
                twitter.oAuth2Token                               //ベアラートークンの取得

                var query1 = Query().query("from:(自分のアカウントの@以降)")    //指定したIDのユーザーの投稿を検索
                var result: QueryResult = twitter.search(query1)

                for (status in result.tweets){                    //取得したツイート数分ログに出力
                    Log.d("status", status.text)
                }
            }.await()

            textview.text = "finish"
        }
    }

ベアラートークンの取得は、上記の真ん中あたりにあるたった2行だけです!この2行だけでツイートの取得や検索ができるようになっています。
上では試しに自分のツイートを検索してログに出力しています。ちゃんと表示されましたか?

また、こちらもプロパティファイルを使わずコード上から設定することができます。以下のようにコードを変更してください。実行結果は上と同じになるはずです。

MainActivity.kt
//Bearer Tokenでの処理
//コード上から設定

    private fun onClick() {
        launch {
            textview.text = "Now Loading"

            async(context = Dispatchers.IO) {
                val cb = ConfigurationBuilder().apply {
                    setDebugEnabled(true)
                    setOAuthConsumerKey("XXXXXXXXXXXXXXXXXX")
                    setOAuthConsumerSecret("XXXXXXXXXXXXXXXXXX")
                    setApplicationOnlyAuthEnabled(true)
                    //setUseSSL(true)  これは不要になった?
                }

                val tf = TwitterFactory(cb.build())
                val twitter = tf.getInstance()
                twitter.oAuth2Token                       //ベアラートークンの取得

                var query1 = Query().query("from:(自分のアカウントの@以降)")
                var result: QueryResult = twitter.search(query1)

                for (status in result.tweets){            //取得したツイート数分ログに出力
                    Log.d("status", status.text)
                }
            }.await()

            textview.text = "finish"
        }
    }


アクセストークンを使ってツイートを取得・投稿してみる

とてもお手軽なベアラートークンですが、前述した「投稿不可」のほかにも「フォローしてる鍵アカウントを見ることができない」「プロフィールを変更できない」といった制限が多くあります。
もしそのような処理をしたい場合は、アクセストークンを入手する処理を入れる必要があります。ただ、これまでの処理と比べてかなりやることが多いです。順序としては以下のようになります。

1、コールバック用のアクティビティの追加
2、AndroidManifestの更新
3、Twitter DeveloperサイトのApp detailsの更新
4、各コードの変更

1、コールバック用のアクティビティの追加

アクセストークン取得の流れは、一度アプリからWebサイトにジャンプ→そこでTwitterログイン→アプリに返ってくるという感じです。その返ってくる用のアクティビティを追加します。

emptyactivity1.png
上の図のようにEmpty Activityを選択して、Activity NameCallBackActivityとして作成してください。
自動的にレイアウトファイルも作成されるはずなので、そこには適当にTextViewを2つ置いておきましょう。
ここではTextViewのIDをそれぞれtextView_tokentextView_secretとしています。

2、AndroidManifestの更新

次にAndroidManifest.xmlを開きます。上の図のようにアクティビティを作成したら、ここに新しくCallBackActivity<activity>タブが作られているはずなので、この部分を以下のようにしてください。

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest>
    <application>
        <activity android:name=".CallBackActivity" android:label="@string/app_name"
            android:launchMode="singleInstance">
            <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="callback"/>
            </intent-filter>
        </activity>
    <!-- その他省略 -->

詳しい説明は省略しますが、これによってWebブラウザ上で認証した後にアプリに戻り、認証情報を受け取ることができます。

3、Twitter Developerサイトでの設定

-前準備:Twitter API キーの取得-のところでTwitter Developerサイトにアプリを登録したと思います。もう一度その画面に戻り、App detailsタブを開いて右上のEditボタンをクリックしましょう。

そうしたら、その中にあるCallback URLscallback://と設定してください。

callback.PNG

先ほどAndroidManifest.xmlの中で<data android:scheme="callback"/>という一文があったと思います。これと同じになるようにCallback URLsを設定しました。
変更が終了したら忘れずにSaveボタンを押して変更を反映させておきましょう。

4、各コードの変更

ようやくコードの変更に入ります。まずはMainActivity.ktを次のように変更しましょう。
(以下のコードはこちらのサイトを大変参考にさせていただいております。)

MainActivity.kt
class MainActivity : AppCompatActivity(), CoroutineScope {

    //コルーチンを使うための準備
    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    //認証オブジェクトの作成
    companion object{
        val mOauth = OAuthAuthorization(ConfigurationContext.getInstance())
        lateinit var mRequest: RequestToken
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.setOnClickListener {
            onClick()
        }
    }

    private fun onClick() {
        launch {
            mOauth.setOAuthConsumer("XXXXXXX(ConsumerKey)", "XXXXXXX(ConsumerSecretKey)")
            mOauth.oAuthAccessToken = null
            var uri: Uri?

            async(context = Dispatchers.IO) {
                mRequest = mOauth.getOAuthRequestToken("callback://CallBackActivity")    //コールバックの設定

                var intent = Intent(Intent.ACTION_VIEW)
                uri = Uri.parse(mRequest.authenticationURL)
                intent.data = uri
                startActivityForResult(intent, 0)            //暗黙インテントでWebブラウザを呼び出して認証
            }                                                //認証情報を受け取るためにstartActivityForResult()
        }
    }

    override fun onDestroy() {
        job.cancel()
        super.onDestroy()
    }
}

次に、先ほどCallBackActivityを作成したときに自動生成されたCallBackActivity.ktを開き、以下のようにしてください。

CallBackActivity.kt
class CallBackActivity : AppCompatActivity() , CoroutineScope{

    //コルーチンを使うための準備
    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_call_back)

        val uri = intent.data                //認証情報の受け取り
        if (uri != null && uri.toString().startsWith("callback://CallBackActivity")){
            launch {
                val mtoken = async(context = Dispatchers.IO){           //asyncは値を返せる
                    val varif = uri.getQueryParameter("oauth_verifier")
                    val token = MainActivity.mOauth.getOAuthAccessToken(MainActivity.mRequest, varif)
                    return@async token
                }.await()

                textView_token.text = mtoken.token          //ID:textView_tokenのTextViewにアクセストークンを表示
                textView_secret.text = mtoken.tokenSecret   //ID:textView_secretのTextViewにシークレットキーを表示
            }
        }
    }

    override fun onDestroy() {
        job.cancel()
        super.onDestroy()
    }
}

これで作成はすべて完了です!
実行してみると「ボタンを押す→Webページ開く→見たことあるような認証画面出てくる→ログインする→アプリに戻ってアクセストークンキーとシークレットキーが表示されている」の一連の流れが確認できると思います!
これでそれぞれのアカウントにフルアクセスできるようになりました!

あとはこの2つのキーを端末に保存しておけば、次回以降はログイン処理が必要なくなります。

以降のツイートの取得や投稿のやり方に関しては、前述したものとほとんど同じです。ぜひ自分で試してみてください。

Coroutines(コルーチン)について

先ほどまでのコードの中には、launch{...}という見慣れないものが出てきたかと思います。これはKotlin特有の機能で、これを使うことで非同期処理をとても簡単に行うことができます。

どうして非同期処理をしなければならないのか

Androidはシングルスレッドモデル(画面のビューなどの操作を1つのスレッドのみが担当する)です。アプリを実行した際にメインスレッド(UIスレッド)が生成され、画面の操作やビューの更新は全てメインスレッドで担っています。
その時に「画像のダウンロード」のような時間がかかる処理をメインスレッドで行ってしまうと、その処理が終わるまでユーザーは画面を操作できず、俗にいう「固まった」状態になってしまいます。また、その状態が続くとAndroid側からANRと呼ばれるエラーを出されてしまうこともあります。

したがって、Androidでは基本的なルールとして「インターネット通信を含む処理は別スレッドでやらなければならない」ということになっています。

コルーチンを使わない場合

通信処理は別スレッドでやる必要がありますが、例えば「ツイートを取得→画面に表示」の場合だと、通信処理に加えてビューの操作もしなければなりません。でも、ビューの操作はメインスレッド以外からはできません。めんどくさい!
スレッド間のやり取りのほかにも色々と考えなきゃいけないことが多く、初心者である我々にとっては非同期処理の実装はとても大変です。

【Android】今更ながらAsyncTaskの警告に対応した話をまとめる【開発】
【Android】 非同期処理 AsyncTaskの使い方  ←おすすめ

上記のリンクは一例ですが、初めての単語がいっぱい出てきてとても疲れますね。

余談ですが、下のリンク先の方が書かれている記事はどれもめちゃくちゃわかりやすい上に更新が最近(2019年8月頃)なので、Androidアプリ開発を始めた初心者の方はぜひ全てに目を通した方がいいと思います。

コルーチンを使う場合

コルーチンを使う場合は、先ほどまで書いていたコードそのままです。使わない場合に比べてとてもコードがすっきりしていますね!
コルーチンについては、正直私が説明するよりもよっぽど分かりやすい記事がQiitaにも多数投稿されていますので、そちらを紹介させていただきます。

入門Kotlin coroutines
逆引き Kotlin Coroutines
Androidと非同期処理 とCoroutine1.0.0
図で理解する Kotlin Coroutine

特に後ろ2つのリンク先は、本記事を書くうえでも大変参考にさせていただきました。
この記事にあるコードで「どうしてそうなるか」は全て上記リンクで説明されている(特に最後)ので、ぜひ皆さんも一度目を通していただけたらと思います。

おわりに

以上でTwitter4Jの基本的な使い方は終わりです。ほかにも画像の取得やプロフィール関連、DMなど出来ることはたくさんありますが、IDEの補完機能で出てくるやつを眺めたりググったりして勉強していただけたらと思います。

この記事では「どういうことをどんな意味でやっているの?」よりも「いいからさっさと動かしたい!」に焦点を当てていますので、詳しい説明は一切していません。
私自身、最初から理屈を学ぶよりも「とりあえず動かせるようになって、そこから意味を考える」勉強法じゃないと頭に入らないタイプなので、私と同じような人の最初の手助けができていれば幸いです。

こうした記事を書くのは初めてなので分かりやすく書けているか不安ですが、何か不明な点や間違っているところがあれば是非お教えくださいm(_ _)m

21
17
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
17