0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Androidアプリで楽天レシピAPIを利用して人気レシピの情報を取得する方法

Posted at

初めに

この記事では、Androidアプリ開発において楽天レシピAPIをたたいて人気レシピの情報を取得する方法について解説します。
なお、Android開発初学者である自分用にRetrofitを利用してインターネットに接続し、データを取得する過程についても詳しくまとめています。
既にデータ通信の方法についてご存じの方にとってはやや冗長な内容となっていますが、その点はご了承ください。

目次

  • 今回参考にした資料

  • 楽天レシピAPIの概要

  • API通信の流れ

  • Retrofitとは

  • 依存関係

  • permissonの追加

  • 実装

    • データクラスの定義
    • APIクライアントクラスの定義
    • インタフェースの定義
    • ViewModelの定義
    • コンポーザブル関数の定義
  • 実行結果

  • まとめ

今回参考にした資料

今回はこちらのCodelabを参考にしました。
https://developer.android.com/codelabs/basic-android-kotlin-compose-getting-data-internet?hl=ja&continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-compose-unit-5-pathway-1%3Fhl%3Dja%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-compose-getting-data-internet#0)

楽天レシピAPIの概要

楽天レシピカテゴリ別ランキングAPI
https://webservice.rakuten.co.jp/documentation/recipe-category-ranking

このAPIはアプリケーションIDを指定することで楽天レシピのカテゴリ別のランキングをJSON形式で返すAPIです。

API通信の大まかな流れ

まず、API通信で情報を取得し、アプリで利用できるようになるまでの流れを以下に示します。

  1. クライアントでRetrofitライブラリを用いてHTTPリクエストを作成します
  2. リクエストを受信したサーバーはそのリクエストに基づいた処理を行い、データをJSONなどの形式で返します
  3. サーバーから返されたJSONデータをそのままの状態でアプリ内で使用することはできないため、Gsonというライブラリを用いてKotlinのオブジェクトに変換します
  4. GsonによってKotlinオブジェクトに変換されたデータはアプリ内で直接操作できるようになります

なお、本記事ではAPIの通信にRetrofitを、JSONデータの変換にGsonを利用していますが、それぞれ他のライブラリなどで代用することも可能です。

Retrofitとは

Retrofitは、Androidアプリ開発において使用されるHTTPクライアントライブラリで、特にAPIとの通信をシンプルかつ効率的に行うことができます。Retrofitの大きな特徴としてインタフェースを使ってAPIのリクエストを定義し、本来複雑なHTTPリクエストの処理をブラックボックスとすることができるということが挙げられます。

依存関係

まず、依存関係の宣言を行います。
なお、今回はRetrofitに加え、JSONデータの変換にGson、URLを用いた画像の表示にCoilというライブラリを使用するほか、ViewModelも使用するため、app/build.gradleに以下のような記述を追加します。
・build.gradle(app)

dependencies{
    ...
    // Retrofit
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.okhttp3:okhttp:4.11.0")

    //Gson Converter
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")

    // Gson
    implementation("com.google.code.gson:gson:2.10.1")

    // ViewModel
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")

    // Coil
    implementation("io.coil-kt:coil-compose:2.4.0")
    ...
}

permissonの追加

インターネットに接続するためには、そのためのpermissonをAndroidManifest.xmlに記述する必要があります。
・AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    ...
</manifest>

実装

データクラスの定義

まず、APIが返したJSONデータをアプリ内で使用するためにデータクラスを定義します。今回はAPIが返すJSONデータのうち、アプリ内で使用するデータは以下の4つとなっています。

  • recipeTitle(レシピタイトル)
  • foodImageUrl(料理画像のURL)
  • recipeIndication(調理時間目安)
  • recipeCost(費用の目安)
    これらをアプリ内で利用するためのデータクラスの定義は以下の通りです。
    ・Result.kt
data class Result(
    @SerializedName("recipeTitle")
    val Title: String,
    @SerializedName("foodImageUrl")
    val foodimage:String,
    @SerializedName("recipeCost")
    val cost:String,
    @SerializedName("recipeIndication")
    val indication:String
)

data class ResultResponse(
    @SerializedName("result")
    val result: List<Result>
)

アプリ内では4つの変数を持ったデータクラスResultのインスタンスがリスト形式となったResultResponseのインスタンスをJSONデータから取得することになります。
なお、JSONデータにおける変数名とアプリ内で使用する変数名が異なっている場合には上に示したようにSerializedNameアノテーションを使用する必要があります。例えば、今回はJSONデータ内のrecipeTitle,foodImageUrl,recipeCost,recipeIndicationをそれぞれアプリ内では異なる名前で使いたいので、SerializedNameアノテーションを用いてJSONデータ内の変数名を宣言し、その下でアプリ内のデータクラスで用いる変数名を宣言します。

APIClientクラスの定義

・RecipeApiService.kt

class ApiClient {
    private companion object {
        private const val BASE_URL = "https://app.rakuten.co.jp/services/api/"
        private const val APPLICATION_ID = //ここにアプリケーションIDを記述
    }

    //Gsonインスタンスの生成
    private val gson = GsonBuilder()
        .setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
        .create()

    //Retrofitインスタンスの生成
    private val retrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build()

    //APIサービスのインスタンス化
    private val apiService : ApiService by lazy{
        retrofit.create(ApiService::class.java)
    }

    //結果を取得する関数を定義
    suspend fun fetchResults(): Response<ResultResponse> {
        return apiService.fetchResults(APPLICATION_ID)
    }
}

まず、companion object内でベースとなるURLとアプリケーションIDを変数に格納しておきます。
次にGsonのインスタンスを作成します。
GsonのインスタンスはGsonBuiilder().create()で作成することができます。
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)でJSONフィールドとKotlinフィールドの名前をそのままにすることを設定しています。

その次にRetrofitのインスタンスを作成します。
Retrofitのインスタンスは
Retrofit.Builder()で作成を開始し、
baseUrl()でベースURLの指定、
addConverterFactory()でGsonのインスタンスを引数とし、JSONデータを自動的にオブジェクトに変換するGsonを設定、
build()でインスタンスの作成を完了しています。

さらに結果を取得するfetchResults()メソッドを定義します。
fetchResult()メソッドでは、ApiServiceのfetchResults()メソッドを呼び出し、APIにリクエストを送ることでレシピ情報のリストを取得しています。
このメソッドはsuspendであるため、非同期処理として実行されます。

インタフェースの定義

interface ApiService {
    @GET("Recipe/CategoryRanking/20170426")
    suspend fun fetchResults(
        @Query("applicationId") applicationId: String,
        @Query("categoryId") categoryId: Int = 30,
    ): Response<ResultResponse>
}

先程インスタンスを作成したApiServiceのインタフェースを定義し、その中でfetchResultsメソッドを定義しています。

GETアノテーションはHTTPのGETリクエストを指定しており、BASE_URL以降に続くAPIエンドポイントへのパスとなっています。

Queryアノテーションはクエリパラメータを指定するためのものであり、ここではapplicationId(必須)とcategoryIdをそれぞれ指定しています。なお、categoryIdについては楽天レシピカテゴリ一覧APIという別のAPIで取得することができ、今回情報を取得したい「人気メニュー」カテゴリのcategoryIdは30であることがあらかじめ分かっているため、ここで直接指定しています。

ViewModelの定義

・RecipeViewModel.kt

sealed interface RecipeUiState {
    data class Success(var recipes: List<Result>) : RecipeUiState
    object Error : RecipeUiState
    object Loading : RecipeUiState
}

class RecipeViewModel: ViewModel() {
    var recipeUiState: RecipeUiState by mutableStateOf(RecipeUiState.Loading)
        private set

    init {
        getRecipeList()
    }
    
    fun getRecipeList(){
        viewModelScope.launch {
            recipeUiState = RecipeUiState.Loading
            recipeUiState = try {
                val listResult = ApiClient().fetchResults()
                if (listResult.isSuccessful) { 
                    val recipeResponse = listResult.body()
                    if (recipeResponse != null ) {
                        RecipeUiState.Success(recipeResponse.result)
                    } else {
                        RecipeUiState.Error
                    }
                } else {
                    RecipeUiState.Error
                }
            } catch (e: IOException) {
                RecipeUiState.Error 
            } catch (e: HttpException) {
                RecipeUiState.Error
            }
        }
    }
}

まず、取得したレシピのUI状態であるRecipeUiStateを定義しています。
これは3つの状態をもっており、
RecipeViewModelクラスはUI状態を管理するためのクラスです。
getLecipeList()メソッドでは、viewModelScopeを用いてコルーチンを開始し、非同期で以下に続く処理を実行します。
まずはRecipeUiStateをLoading(ロード中)にした状態でApiClientクラスのfetchResult()メソッドを用いてAPIリクエストを実行し、データの取得を試みます。
データの取得に成功した場合(isSuccessfulの場合)はその取得したデータがnullでないことを確認し、取得したrecipeResponseのデータによりRecipeUiStateをSuccess状態に更新します。
なお、もしデータを適切に取得できなかった場合にはRecipeUiStateの状態がErrorに設定されます。

コンポーザブル関数の定義

・HomeScreen.kt

@Composable
fun HomeScreen(
    recipeUiState: RecipeUiState,
    modifier: Modifier = Modifier
){
    when (recipeUiState) {
        is RecipeUiState.Success -> {
            RecipeList(recipes = recipeUiState.recipes, modifier = modifier)
        }
        is RecipeUiState.Error -> {
            Text(text = "Error loading recipe")
        }
        is RecipeUiState.Loading -> {
            Text(text = "Loading...")
        }
    }
}

@Composable
fun RecipeList(
    recipes:List<Result>,
    modifier: Modifier = Modifier
){
        LazyColumn(
            modifier = modifier
        ) {
            items(recipes) { recipe ->
                RecipeCard(result = recipe)
            }
        }
}


@Composable
fun RecipeCard(
    result: Result,
    modifier: Modifier = Modifier
){
    Card(
        colors = CardDefaults.cardColors(),
        modifier = modifier.fillMaxWidth().padding(16.dp)
    ) {
        Column {
            Text(
                text = result.Title,
                modifier = Modifier.padding(16.dp),
                fontSize = 24.sp,
                fontWeight = FontWeight.Bold
            )
            Row {
                AsyncImage(
                    model = result.foodimage,
                    contentDescription = null,
                    modifier = Modifier
                        .width(160.dp)
                        .height(120.dp)
                )
                Column {
                    Text(
                        text="コスト: ${result.cost}",
                        modifier = Modifier.padding(12.dp),
                        fontSize = 16.sp
                    )
                    Text(
                        text="所要時間: ${result.indication}",
                        modifier = Modifier.padding(12.dp),
                        fontSize = 16.sp
                    )
                }
            }
        }
    }
}

HomeScreen()メソッドではRecipeUiStateを引数とし、その状態に応じてUIを変えます。
RecipeUiState.Successの場合はRecipeListメソッドを呼び出し、それ以外の状態では対応するテキストを表示します。

RecipeList()メソッドは、レシピのリストをスクロール可能なリストとして表示します。Resultのリストであるrecipesから要素を1つづつ取り出し、個々のレシピのUIであるRecipeCardを表示しています。

RecipeCard()メソッドは、個々のレシピ情報をカード形式で表示します。

・MainActivity.kt

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            RakutenSampleTheme {
                Surface(
                    modifier=Modifier.fillMaxSize(),
                    color= MaterialTheme.colorScheme.background
                ) {
                    val recipeViewModel: RecipeViewModel = viewModel()
                    HomeScreen(
                        recipeUiState = recipeViewModel.recipeUiState,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            }
        }
    }
}

MainActivityでは、viewmodelのインスタンスを生成し、HomeScreen関数を呼び出しています。

実行結果

実行結果は以下のようになります。
スクリーンショット 2024-10-02 123831.png
人気レシピの情報を取得し、リスト形式で画面上に表示できていることがわかります。

まとめ

この記事では、楽天レシピAPIを利用してAndroidアプリ内で人気レシピの情報を取得する方法を解説しました。HTTPリクエストを通じたデータ取得、そしてそのデータをアプリ内で表示する流れを具体的に見てきました。
この記事を参考に、実際のアプリ開発に活用してみてください。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?