初めに
この記事では、Androidアプリ開発において楽天レシピAPIをたたいて人気レシピの情報を取得する方法について解説します。
なお、Android開発初学者である自分用にRetrofitを利用してインターネットに接続し、データを取得する過程についても詳しくまとめています。
既にデータ通信の方法についてご存じの方にとってはやや冗長な内容となっていますが、その点はご了承ください。
目次
-
今回参考にした資料
-
楽天レシピAPIの概要
-
API通信の流れ
-
Retrofitとは
-
依存関係
-
permissonの追加
-
実装
- データクラスの定義
- APIクライアントクラスの定義
- インタフェースの定義
- ViewModelの定義
- コンポーザブル関数の定義
-
実行結果
-
まとめ
今回参考にした資料
楽天レシピAPIの概要
楽天レシピカテゴリ別ランキングAPI
https://webservice.rakuten.co.jp/documentation/recipe-category-ranking
このAPIはアプリケーションIDを指定することで楽天レシピのカテゴリ別のランキングをJSON形式で返すAPIです。
API通信の大まかな流れ
まず、API通信で情報を取得し、アプリで利用できるようになるまでの流れを以下に示します。
- クライアントでRetrofitライブラリを用いてHTTPリクエストを作成します
- リクエストを受信したサーバーはそのリクエストに基づいた処理を行い、データをJSONなどの形式で返します
- サーバーから返されたJSONデータをそのままの状態でアプリ内で使用することはできないため、Gsonというライブラリを用いてKotlinのオブジェクトに変換します
- 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関数を呼び出しています。
実行結果
実行結果は以下のようになります。
人気レシピの情報を取得し、リスト形式で画面上に表示できていることがわかります。
まとめ
この記事では、楽天レシピAPIを利用してAndroidアプリ内で人気レシピの情報を取得する方法を解説しました。HTTPリクエストを通じたデータ取得、そしてそのデータをアプリ内で表示する流れを具体的に見てきました。
この記事を参考に、実際のアプリ開発に活用してみてください。