20190627のKotlinに関する記事は3件です。

[Android] NavigationでSafeArgsを使って引数付き画面遷移をする

SafeArgsとは

みなさん,ActivityやFragment間の遷移で引数を受け渡しする時はどうしているでしょうか.
まだBundleを使っている方もいるかもしれませんね.

Navigation Componentの1機能であるSafeArgsを使えば,型安全な引数の受け渡しが実現できます.みんな使っているからいっぱい記事があるかと思いきや,alphaやbetaの記事ばかりなのでstable版としてまとめておきたいと思います.
(あれから便利な拡張関数がサイレントで登場したりしている)

Navigationの基本的な使い方は理解している前提から始めますので,微妙という方は以下の記事からキャッチアップしてきてください!

[Android] 10分で作る、Navigationによる画面遷移
https://qiita.com/tktktks10/items/7df56b4795d907a4cd31

作るもの

最終的な実装は下記レポジトリに置いてあります.

tks10/safeargs-example
https://github.com/tks10/safeargs-example

仕様はミニマムな感じでいきましょう.
MainActivityの上にFragmentを乗せて遷移をしていく,1Activtity多Fragmentなかんじです.
FirstFragmentにはEditTextとButtonがあり,ButtonをおしたらEditTextの内容とともにSecondFragmentに遷移してその内容を表示します.

左の画面でNextを押すと,右に画面になるやつですね.
 → 

ではやっていきましょう.

事前準備

以下をgradleに追記.

project/build.gradle
buildscript {
    ...
    dependencies {
        ...
        classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0"
    }
}
app/build.gradle
apply plugin: "androidx.navigation.safeargs"

dependencies {
    ...
    implementation "android.arch.navigation:navigation-fragment:1.0.0"
    implementation "android.arch.navigation:navigation-ui:1.0.0"
    implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0"
    implementation "android.arch.navigation:navigation-ui-ktx:1.0.0"
}

初期状態

1から作っていては冗長なので,各Fragmentとnavigationグラフの初期配置が完了した時点から始めます.ちなみに現時点でnavigationグラフは下記のようになっており,FirstFragmentからSecondFragmentにただの遷移をするだけのactionが1つだけ定義されている状況です.

navigation_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/navigation_graph"
        app:startDestination="@id/firstFragment">

    <fragment
            android:id="@+id/firstFragment"
            android:name="com.tks10.safeargsexample.FirstFragment"
            android:label="fragment_first"
            tools:layout="@layout/fragment_first">

        <action
                android:id="@+id/action_first_to_second"
                app:destination="@id/secondFragment"/>

    </fragment>

    <fragment
            android:id="@+id/secondFragment"
            android:name="com.tks10.safeargsexample.SecondFragment"
            android:label="fragment_second"
            tools:layout="@layout/fragment_second">
    </fragment>

</navigation>

FirstFragmentではSecondFragmentに遷移する処理だけを記述した状態です.

FirstFragment.kt
class FirstFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        view.nextButton.setOnClickListener {
            findNavController().navigate(R.id.action_first_to_second)
        }
    }
}

また,FisrtFragmentとSecondFragmentのxmlは下記のようになっています.

fragment_fisrt.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/contentEditText"
            android:layout_width="240dp"
            android:layout_height="wrap_content"
            android:gravity="center"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@id/nextButton"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintVertical_chainStyle="packed"/>

    <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/nextButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Next"
            android:layout_marginTop="16dp"
            app:layout_constraintTop_toBottomOf="@id/contentEditText"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
fragment_second.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/resultTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

当然この状態では引数の受け渡しは行なっていないので,遷移しても何も表示されません.

実装

それではこれを改造してsafeargsを使っていきましょう!
safeargsを使うにはnavigationグラフにargumentタグを追加することから始めます.

この時argumentタグは,引数を受け取る側に追加します.下記のようにSecondFragment内にargumentタグを追加しましょう.この時指定するのは,変数名を示すandroid:nameと型を示すandroid:argTypeの2つです.

navigation_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/navigation_graph"
        app:startDestination="@id/firstFragment">

    <fragment
            android:id="@+id/firstFragment"
            android:name="com.tks10.safeargsexample.FirstFragment"
            android:label="fragment_first"
            tools:layout="@layout/fragment_first">

        <action
                android:id="@+id/action_first_to_second"
                app:destination="@id/secondFragment"/>

    </fragment>

    <fragment
            android:id="@+id/secondFragment"
            android:name="com.tks10.safeargsexample.SecondFragment"
            android:label="fragment_second"
            tools:layout="@layout/fragment_second">

        <!-- ココに定義する -->
        <argument
                android:name="content"
                app:argType="string"/>

    </fragment>

</navigation>

なんとnavigationグラフの変更はこれで終わりです.
それでは次に行く・・・,前にRebuild Projectをしておきましょう.
現状DataBindingのようにリアルタイムにコードが生成されず,自動生成系のクラスが見つからん!などとハマります.

さて,RebuildがおわったらFirstFragmentをいじっていきます.先ほどはただnavigateしていただけの部分を,以下のように書き換えてみましょう.

FirstFragment.kt
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        view.nextButton.setOnClickListener {
            // EditTextの中身を取り出す
            val content = view.contentEditText.text.toString()

            // 生成されたクラスに引数を渡して遷移
            val action = FirstFragmentDirections.actionFirstToSecond(content)
            findNavController().navigate(action)
        }
    }

先ほどargumentタグをSecondFragment側に追加したことにより,SecondFragmentをDestinationとするactionのクラスとメソッドが自動生成されているはずです.今回だと,
FirstFragmentDirections#actionFirstToSecond
ですね.actionタグにつけたidから自動的にメソッド名をつけてくれます.このメソッドの引数に先ほど追加したargumentを受け取るようになっていることがわかります.これで実行時に名前が違って落ちた...なんてことがなくなりますね.

さてここで満足して終わりそうですが,引数を受け取る側の処理が残っています.
SecondFragmentで引数を受け取ってみましょう.といっても,実は1行で終わります.

それではSecondFragmentの頭の方に以下の1行を加えてみましょう.

SecondFragment.kt
class SecondFragment : Fragment() {
    private val args: SecondFragmentArgs by navArgs()

    ....
}

navArgs()というFragmentに対する拡張関数が気がついたら提供されており,
args.content
みたいなかんじで受け取った引数にアクセスできるようになっています.

あとはこれをTextViewにセットしてあげるだけです.

SecondFragment.kt
class SecondFragment : Fragment() {
    private val args: SecondFragmentArgs by navArgs()

    ....

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.resultTextView.text = args.content
    }
}

お疲れ様です!

複数の引数を指定する

なるほどわかった,でも複数渡すことだって多いでしょ?
と突っ込んきた人のために書くこととしましょう.

先ほど受け取った引数に加えてもう1つ値をEditTextに入力し,計2つの引数を次のFragmentに渡したいと思います.全く同じだとなんなので,整数にしましょう.下記のような感じです.

 → 

流れは先ほどと全く同じで,目的地であるThirdFragment側にargumentタグを追加します.何も考えずに並列に追加するだけで問題ありません.そして忘れずにRebuildをします

あと,SecondFragmentからThirdFragmentへのaction定義も忘れないようにしましょう.

navigation_graph.xml
    ...
    <fragment
            android:id="@+id/secondFragment"
            android:name="com.tks10.safeargsexample.SecondFragment"
            android:label="fragment_second"
            tools:layout="@layout/fragment_second">

        <argument
                android:name="content"
                app:argType="string"/>

        <action
                android:id="@+id/action_second_to_third"
                app:destination="@id/thirdFragment"/>

    </fragment>

    <fragment
            android:id="@+id/thirdFragment"
            android:name="com.tks10.safeargsexample.ThirdFragment"
            android:label="fragment_third"
            tools:layout="@layout/fragment_third">

        <argument
                android:name="content"
                app:argType="string"/>
        <argument
                android:name="value"
                app:argType="integer"/>

    </fragment>

そうしたら,SecondFragmentを改造していきます.viewのxmlは以下のようになり,しれっとEditTextとButtonを追加しています.

fragment_second.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/resultTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:layout_marginBottom="120dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@id/valueEditText"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintVertical_chainStyle="packed"/>

    <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/valueEditText"
            android:layout_width="240dp"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:inputType="number"
            app:layout_constraintTop_toBottomOf="@id/resultTextView"
            app:layout_constraintBottom_toTopOf="@id/nextButton"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

    <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/nextButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Next"
            android:layout_marginTop="16dp"
            app:layout_constraintTop_toBottomOf="@id/valueEditText"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

さて,プログラムの方は以下のように改造してみましょう.

SecondFragment.kt
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.resultTextView.text = args.content
        view.nextButton.setOnClickListener {
            val content = args.content
            val value = view.valueEditText.text.toString().toInt()

            // argumentを増やすと,このメソッドの引数も対応して増える
            val action = SecondFragmentDirections.actionSecondToThird(content, value)
            findNavController().navigate(action)
        }
    }

シンプルですね.actionのメソッドが受け取る引数が素直に増えます.
ここにEditTextから取得した値を渡してあげましょう.

そして忘れずに受け取る側も書きます.
xmlとプログラムは下記の通りです.

ThirdFragment.kt
class ThirdFragment : Fragment() {
    private val args: ThirdFragmentArgs by navArgs()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_third, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        view.contentTextView.text = args.content
        view.valueTextView.text = args.value.toString()
    }
}
fragment_third.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/contentTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/valueTextView"
            app:layout_constraintHorizontal_chainStyle="packed"/>

    <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/valueTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:layout_marginStart="16dp"
            app:layout_constraintBaseline_toBaselineOf="@id/contentTextView"
            app:layout_constraintStart_toEndOf="@id/contentTextView"
            app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

お疲れ様です!

自作クラスを指定する

ん,じゃぁ自作で作ったdata classはどうするんだと.では書きましょう.
結論から言うと,Parcelableを継承していればsafeargsを利用することができます.

では今回は,下記のクラスをsafeargsで渡すことを考えてみましょう.

MyData.kt
data class MyData(
    val content: String,
    val value: Int,
    val message: String
)

完成予想図はこんな感じです.(messageには適当に文字列をいれています)

 → 

まずはじめに,このクラスにParcelableを実装しましょう,,,と言ってもめんどくさいので,@Parcelizeアノテーションを使います.Experimentalをenableにしましょう.

app/build.gradle
android {
    ...
    androidExtensions {
        experimental = true
    }
}

MyDataに対して以下のようにアノテーションをつけます.

MyData.kt
@Parcelize
data class MyData(
    val content: String,
    val value: Int,
    val message: String
): Parcelable

あとはこれまでの流れと同じになります.まずはnavigationグラフに追加しましょう.

navigation_graph.xml
    ...
    <fragment
            android:id="@+id/thirdFragment"
            android:name="com.tks10.safeargsexample.ThirdFragment"
            android:label="fragment_third"
            tools:layout="@layout/fragment_third">

        <argument
                android:name="content"
                app:argType="string"/>
        <argument
                android:name="value"
                app:argType="integer"/>

        <action
                android:id="@+id/action_third_to_fourth"
                app:destination="@id/fourthFragment"/>

    </fragment>

    <fragment
            android:id="@+id/fourthFragment"
            android:name="com.tks10.safeargsexample.FourthFragment"
            android:label="fragment_fourth"
            tools:layout="@layout/fragment_fourth">

        <argument
                android:name="myData"
                app:argType="com.tks10.safeargsexample.MyData"/>

    </fragment>

プリミティブなクラス(stringとかintegerとか)以外はフルパスでargTypeを指定してあげましょう.たとえばParcelableを実装しているBitmapとかも渡せたりします.

もうお察しでしょうが,ThirdFragmentでは以下のように記述します.

ThirdFragment.kt
class ThirdFragment : Fragment() {
    private val args: ThirdFragmentArgs by navArgs()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_third, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        view.contentTextView.text = args.content
        view.valueTextView.text = args.value.toString()

        view.nextButton.setOnClickListener {
            val myData = MyData(
                args.content,
                args.value,
                "LGTM!!"
            )

            val action = ThirdFragmentDirections.actionThirdToFourth(myData)
            findNavController().navigate(action)
        }
    }
}

受け取るFourthFragmentは以下のようになります.
(xmlは省略しますが,messageを表示するTextViewが増えただけです)

FourthFragment.kt
class FourthFragment : Fragment() {
    private val args: FourthFragmentArgs by navArgs()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_fourth, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val myData = args.myData

        view.contentTextView.text = myData.content
        view.valueTextView.text = myData.value.toString()
        view.messageTextView.text = myData.message
    }
}

おわりに

以上,SafeArgsによる引数の受け渡しでした.複数の受け渡しや自作クラスの受け渡しもできるようになったかと思います.
これまでalphaやbetaでコロコロと記述が変わりましたが,ようやくstableになって固定された感があります.
型安全になるだけでもかなりメリットですし,navigationグラフをみたときに引数が一覧できるのも良いと思います.
ぜひ既存のプロジェクトでも部分的に取り入れていきたいところですね.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】パパっと使えるAlertDialog一覧

はじめに

普段使いたい場面で使い方を忘れるAlertDialogを、パパっと使うための備忘録です。
@suzukihrさんが書かれている【コピペしてすぐ使えるアラートダイアログ集】を参考に、
さらに初心者向けに追記しKotlinで書き直したものとなります。

※ActivityやFragmentなどに直接コピペして使用できますが、
 画面回転時にメモリリークが発生する可能性があるため、
 DialogFragmentを継承したクラスを作って使用することをおすすめします。

通常ダイアログ(Yesのみ)

通常ダイアログ_YES.png

AlertDialog.Builder(this) // FragmentではActivityを取得して生成
        .setTitle("タイトル")
        .setMessage("メッセージ")
        .setPositiveButton("OK", { dialog, which ->
            // TODO:Yesが押された時の挙動
        })
        .show()

通常ダイアログ(Yes・No)

通常ダイアログ_YES_NO.png

AlertDialog.Builder(this) // FragmentではActivityを取得して生成
        .setTitle("タイトル")
        .setMessage("メッセージ")
        .setPositiveButton("OK", { dialog, which ->
            // TODO:Yesが押された時の挙動
        })
        .setPositiveButton("No", { dialog, which ->
            // TODO:Noが押された時の挙動
        })
        .show()

通常ダイアログ(Yes・No・その他)

通常ダイアログ_YES_NO_その他.png

AlertDialog.Builder(this) // FragmentではActivityを取得して生成
        .setTitle("タイトル")
        .setMessage("メッセージ")
        .setPositiveButton("OK", { dialog, which ->
            // TODO:Yesが押された時の挙動
        })
        .setPositiveButton("No", { dialog, which ->
            // TODO:Noが押された時の挙動
        })
        .setNeutralButton("その他", { dialog, which ->
            // TODO:その他が押された時の挙動
        })
        .setIcon(R.mipmap.ic_launcher)
        .show()

通常ダイアログ(アイコン付き)

通常ダイアログ_YES_NO_その他_アイコン.png

AlertDialog.Builder(this) // FragmentではActivityを取得して生成
        .setTitle("タイトル")
        .setMessage("メッセージ")
        .setPositiveButton("OK", { dialog, which ->
            // TODO:Yesが押された時の挙動
        })
        .setPositiveButton("No", { dialog, which ->
            // TODO:Noが押された時の挙動
        })
        .setNeutralButton("その他", { dialog, which ->
            // TODO:その他が押された時の挙動
        })
        .show()

リスト項目選択ダイアログ

リスト表示では setItems を使用します。

第1引数(CharSequence[]):リスト表示する文字配列
第2引数(OnClickListener):項目クリック時のクリックリスナー

リスト選択ダイアログ.png

val strList = arrayOf("りんご","みかん","ぶどう")

AlertDialog.Builder(this) // FragmentではActivityを取得して生成
        .setTitle("リスト選択ダイアログ")
        .setItems(strList, { dialog, which ->
            // TODO:アイテム選択時の挙動
        })
        .setPositiveButton("OK", { dialog, which ->
            // TODO:Yesが押された時の挙動
        })
        .show()

ラジオボタン項目選択ダイアログ

ラジオボタン表示では setSingleChoiceItems を使用します。

第1引数(CharSequence[]):リスト表示する文字配列
第2引数(int):デフォルトでチェックを入れる位置
第3引数(OnClickListener):項目クリック時のクリックリスナー

ラジオボタン選択ダイアログ.png

val strList = arrayOf("りんご","みかん","ぶどう")

AlertDialog.Builder(this) // FragmentではActivityを取得して生成
        .setTitle("ラジオボタン選択ダイアログ")
        .setSingleChoiceItems(strList, 0, { dialog, which ->
            // TODO:アイテム選択時の挙動
        })
        .setPositiveButton("OK", { dialog, which ->
            // TODO:Yesが押された時の挙動
        })
        .show()

チェックボックス選択ダイアログ

チェックボックス表示では setMultiChoiceItems を使用します。

第1引数(CharSequence[]):リスト表示する文字配列
第2引数(boolean[]):デフォルトでチェックを入れておく状態を管理する配列
第3引数(OnClickListener):項目クリック時のクリックリスナー

チェックボックス選択ダイアログ.png

val strList = arrayOf("りんご","みかん","ぶどう")
val checkedItems = booleanArrayOf(true, false, false)

AlertDialog.Builder(this) // FragmentではActivityを取得して生成
        .setTitle("チェックボックス選択ダイアログ")
        .setMultiChoiceItems(strList, checkedItems, { dialog, which, isChecked ->
            // TODO:アイテム選択時の挙動
        })
        .setPositiveButton("OK", { dialog, which ->
            // TODO:Yesが押された時の挙動
        })
        .show()

その他、設定関連

■setCancelable
ダイアログの表示範囲外をタップした際に、ダイアログが閉じれるかどうかを設定します。

setCancelable(cancelable : Boolean)

■setView
自分で作ったレイアウトやViewを直接セットするときに使用します。

setView(layoutResId : Int)
    or
setView(view : View)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kotlin + Retrofit でAPIコールしてみた

はじめに

二年目になりました。
kotlinでAndroid開発を勉強中です。
とりあえずAPIでとってきたデータを表示するところまでできたので、メモ書き程度に残します。

やりたいこと

楽天ウェブサービスの楽天商品ランキングAPIで総合ランキングをとってきて、リストに表示する!
(本当は順位や画像、価格など表示したいのですが、一旦商品名だけ出します)
index.gif

環境と使うもの

環境

  • Mac OS Mojave v10.14.5
  • Android Studio 3.4.1

使う物

やってみよう

1.環境を整える
2.表示する場所をつくる
3.APIからの返り値を置く場所を作る
4.APIを呼ぶ

環境を整える

  • 楽天ウェブサービスに登録する

楽天ウェブサービスに登録し、APIをコールする際に必要になるapplicationIdを取得します。
取得の仕方はこちらを参考にさせていただきました。
楽天(Rakuten Developers)のアプリIDとアフィリエイトIDを取得する方法

  • retrofitを使えるようにbuild.gradleに以下を追加する
build.gradle
def retrofitVersion = '2.4.0'
    implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
    implementation "com.squareup.retrofit2:adapter-rxjava:$retrofitVersion"
    implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
  • HTTP通信ができるように、AndroidManifest.xmlに以下を追加する
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>

manifestタグ内に入れましょう。
これがないとHTTP通信ができません!

  • Activityを作成する

プロジェクトビューで左クリックし、
新規 > アクティビティー > 空のアクティビティー
と選択します。
スクリーンショット 2019-06-27 16.19.27.png

アクティビティー名を入力し、レイアウトファイルを生成するにチェックをいれます。
レイアウト名が自動で入ります。(好きなように変更できます)
パッケージ名はご自身の環境にあるものをお使いください。
完了を押すと、以下の2つのファイルが生成されます。
 - RakutenActivity.kt
 - activity_rakuten.xml

スクリーンショット 2019-06-27 16.25.05.png

表示する場所を作る

先程作成したactivity_rakuten.xmlで、レイアウトを作成していきます。
GUIで作成することが可能ですが、今回は完成しているものをxmlで載せておきます。

activity_rakuten.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".RakutenActivity">

    <Button
            android:text="ランキングを表示する"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/button"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginLeft="8dp" android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp" android:layout_marginRight="8dp" app:layout_constraintHorizontal_bias="0.498"
            android:onClick="getRanking"
            android:layout_marginTop="16dp" app:layout_constraintTop_toTopOf="parent"/>
    <TextView
            android:layout_width="0dp"
            android:layout_height="36dp"
            android:id="@+id/titleRanking"
            android:textSize="18sp" android:gravity="center_horizontal|center_vertical" android:layout_marginStart="8dp"
            app:layout_constraintStart_toStartOf="parent" android:layout_marginLeft="8dp" android:layout_marginEnd="8dp"
            app:layout_constraintEnd_toEndOf="parent" android:layout_marginRight="8dp"
            app:layout_constraintHorizontal_bias="0.0" android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/button"/>
    <ListView
            android:layout_width="395dp"
            android:layout_height="618dp"
            android:id="@+id/listRanking" app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginEnd="8dp" android:layout_marginRight="8dp" android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@+id/titleRanking"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Buttonを押すと、getRankingイベントが起こり、
TextView タグ内にランキングのタイトルを、
ListView タグ内にランキング上位から商品の名前を詰めていきます。

APIからの返り値を置く場所を作る

データクラスなるものを作ります。
JsonToKotlinClassというプラグインを使用しました。
以下の記事の通りに行いました。
kotlinでJSON扱うならJsonToKotlinClassが便利

記事のとおりに行うと以下のようなクラスが出来上がります。

RakutenRankingResult.kt
data class RakutenRankingResult(
    val Items: List<Item>,
    val lastBuildDate: String,
    val title: String
)

data class Item(
    val affiliateRate: String,
    val affiliateUrl: String,
    val asurakuArea: String,
    val asurakuClosingTime: String,
    val asurakuFlag: Int,
    val availability: Int,
    val carrier: Int,
    val catchcopy: String,
    val creditCardFlag: Int,
    val endTime: String,
    val genreId: String,
    val imageFlag: Int,
    val itemCaption: String,
    val itemCode: String,
    val itemName: String,
    val itemPrice: String,
    val itemUrl: String,
    val mediumImageUrls: List<String>,
    val pointRate: Int,
    val pointRateEndTime: String,
    val pointRateStartTime: String,
    val postageFlag: Int,
    val rank: Int,
    val reviewAverage: String,
    val reviewCount: Int,
    val shipOverseasArea: String,
    val shipOverseasFlag: Int,
    val shopCode: String,
    val shopName: String,
    val shopOfTheYearFlag: Int,
    val shopUrl: String,
    val smallImageUrls: List<String>,
    val startTime: String,
    val taxFlag: Int
)

APIを呼ぶ

前半で作成したRakutenActivity.ktに書き足していきます。

interface

APIを表現、定義します。

@の後に基本のURLに続くURLを書きます。
本来であればクエリパラメータとして渡すのが正しい方法のような気はしますが、本記事ではできるだけシンプルに記述したいので、べた書きしてます。
applicationIdは、最初に取得したご自身のIDを書いてください。[]は不要です

RakutenActivity.kt
private val itemInterface by lazy { createService() }

    interface ItemInterface {
        @GET("IchibaItem/Ranking/20170628?formatVersion=2&applicationId=[applicationId]")
        fun items(): retrofit2.Call<RakutenRankingResult>
}

HttpClient

基底URLの定義などを行います。

baseApiUrlが基本のURLです。
/まで入れるのが基本?と見たような気がします。ソースは不明です。申し訳ありません。

RakutenActivity.kt
fun createService(): ItemInterface {
    val baseApiUrl = "https://app.rakuten.co.jp/services/api/"

    val httpLogging = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
    val httpClientBuilder = OkHttpClient.Builder().addInterceptor(httpLogging)

    val retrofit = Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl(baseApiUrl)
        .client(httpClientBuilder.build())
        .build()

    return retrofit.create(ItemInterface::class.java)
}

Retrofit instance

このserviceは、APIを定義したinterfaceをインスタンス化したものであり、実際にAPIコールを行う際に利用されます。

メソッド名getRankingは、レイアウトで作成したボタンのonClick属性に入力します。

onFailureがAPIコール失敗時に呼ばれる処理、onResponseが成功時に呼ばれる処理です。
onFailureには何も書いていません。エラーハンドリングしたい方はここに書きましょう。

titleにランキングのタイトルをつめて、titleRankingというidのtextViewで表示しています。

for文でレスポンスのItemsを回し、itemNameitemsにつめていきます。
ArrayAdapterを使用し、itemsにつめたitemNameを、listRankingに順番に表示します。

RakutenActivity.kt
fun getRanking(v: View){
    itemInterface.items().enqueue(object : retrofit2.Callback<RakutenRankingResult> {
        override fun onFailure(call: retrofit2.Call<RakutenRankingResult>?, t: Throwable?) {
        }

        override fun onResponse(call: retrofit2.Call<RakutenRankingResult>?, response: retrofit2.Response<RakutenRankingResult>) {
            if (response.isSuccessful) {
                response.body()?.let {

                    var items = mutableListOf<String>()
                    var res = response.body()?.Items?.iterator()

                    var title = response.body()!!.title
                    titleRanking.text = "$title"

                    if (res != null) {
                        for (item in res) {
                            items.add(item.itemName)
                        }
                    }

                    val adapter = ArrayAdapter(this@RakutenActivity, android.R.layout.simple_list_item_1, items)
                    val list: ListView = findViewById(R.id.listRanking)
                    list.adapter = adapter
                }
            }
        }
    })
}

以上で完成です。ランキングは出てきましたか?

さいごに

はじめてandroid開発を行っています。
java経験もほぼ皆無なので苦戦中です。
なにか間違っていることや、もっとスマートな書き方等あればお教えいただきたいです。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む