AndroidアプリケーションでWEB APIを利用する

今回はAndroidアプリケーションでWEB APIを利用するプログラムに挑戦します。UI開発は最新ツール「Jetpack Compose」を使用しています。 WEB APIはこちらの記事で作成したものを使用しています。【WEB APIの作成に挑戦!】
あれこれ調べながら作成しましたが、理解が追いつかず、いろいろおかしい箇所もあるかと思います。 もし流用される場合は、適宜修正してください。ご利用は自己責任でお願いいたします。

※この記事は2023/07/22時点の情報です。

【build.gradle(:app)】
build.gradle(:app)にViewModel・JSON関連の依存関係などを追加しています。

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'org.jetbrains.kotlin.plugin.serialization' version '1.5.21' // バージョンは最新のものに更新してください
}

android {
    namespace 'com.sample.webapisample'
    compileSdk 33

    defaultConfig {
        applicationId "com.sample.webapisample"
        minSdk 30
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.3.2'
    }
    packagingOptions {
        resources {
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
        }
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.8.0'
    implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation 'androidx.activity:activity-compose:1.5.1'
    implementation platform('androidx.compose:compose-bom:2022.10.00')
    implementation 'androidx.compose.ui:ui'
    implementation 'androidx.compose.ui:ui-graphics'
    implementation 'androidx.compose.ui:ui-tooling-preview'
    implementation 'androidx.compose.material3:material3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
    debugImplementation 'androidx.compose.ui:ui-tooling'
    debugImplementation 'androidx.compose.ui:ui-test-manifest'
    // ui-tool
    implementation "androidx.compose.ui:ui-tooling:1.0.0"
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
    // ViewModel utilities for Compose
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
    // Lifecycles only (without ViewModel or LiveData)
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
    // Saved state module for ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1"
    // alternately - if using Java8, use the following instead of lifecycle-compiler
    implementation "androidx.lifecycle:lifecycle-common-java8:2.5.1"
    // optional - helpers for implementing LifecycleOwner in a Service
    implementation "androidx.lifecycle:lifecycle-service:2.5.1"
    // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
    implementation "androidx.lifecycle:lifecycle-process:2.5.1"
    // optional - ReactiveStreams support for LiveData
    implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:2.5.1"
    // optional - Test helpers for LiveData
    testImplementation "androidx.arch.core:core-testing:2.1.0"
    // optional - Test helpers for Lifecycle runtime
    testImplementation "androidx.lifecycle:lifecycle-runtime-testing:2.5.1"
    // livedata
    implementation "androidx.compose.runtime:runtime:1.3.3"
    implementation "androidx.compose.runtime:runtime-livedata:1.3.3"
    implementation "androidx.compose.runtime:runtime-rxjava2:1.3.3"
    implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2" // バージョンは最新のものに更新してください
}

【AndroidManifest.xml】
AndroidManifest.xmlにandroid.permission.INTERNETパーミッションを追加します。
<uses-permission android:name="android.permission.INTERNET" />

この記事ではlocalhostのWEB APIを利用するので<application>の下に以下の1行を追加します。
android:usesCleartextTraffic="true"

【Item.kt】
APIのレスポンス情報を格納するためのデータクラスを作成します。

package com.sample.webapisample

import kotlinx.serialization.Serializable

@Serializable
data class Item(
    val id: Int,
    val name: String,
    val age: Int,
    val field1: String,
    val field2: String,
    val field3: String
)

【MainActivity.kt】
今回のメインとなるクラスです。WEB APIからデータを非同期で取得し、取得したデータをリストとして表示します。 HttpURLConnectionを使用してAPIリクエストを実行し、取得したJSONデータをパースしてItemクラスのリストに変換しています。 なお、この例ではデータを一度だけ取得していますが、リアルタイム更新が必要な場合は、ViewModelなどのアプローチを検討してください。

package com.sample.webapisample

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import java.io.BufferedReader
import java.io.DataOutputStream
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                DataFetchingScreen()
            }
        }
    }
}

@Composable
fun DataFetchingScreen() {
    var items by remember { mutableStateOf(emptyList()) }

    LaunchedEffect(Unit) {
        val response = fetchDataFromApi()
        items = response ?: emptyList()
    }

    Surface(color = Color.Blue) {
        LazyColumn {
            items(items) { item ->
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Text(text = item.id.toString(), fontSize = 24.sp, color = Color.White)
                    Spacer(modifier = Modifier.height(8.dp))
                    Text(text = item.name, fontSize = 24.sp, color = Color.White)
                }
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MaterialTheme {
        DataFetchingScreen()
    }
}

// APIエンドポイント (localhostの場合は10.0.2.2を指定する)
private const val API_URL = "http://10.0.2.2:8080/sampleApi/api/sample"

private suspend fun fetchDataFromApi(): List? = withContext(Dispatchers.IO) {
    return@withContext try {
        val url = URL(API_URL)
        val connection = url.openConnection() as HttpURLConnection
        connection.requestMethod = "POST"
        connection.setRequestProperty("Content-Type", "application/json")
        connection.doOutput = true
        // リクエストボディにパラメータを追加
        val requestBody = """
            {
                "code": "Sample"
            }
        """.trimIndent()

        val dataOutputStream = DataOutputStream(connection.outputStream)
        dataOutputStream.writeBytes(requestBody)
        dataOutputStream.flush()
        dataOutputStream.close()

        val responseCode = connection.responseCode
        if (responseCode == HttpURLConnection.HTTP_OK) {
            val inputStream = connection.inputStream
            val reader = BufferedReader(InputStreamReader(inputStream))
            val responseData = StringBuilder()
            var line: String?
            while (reader.readLine().also { line = it } != null) {
                responseData.append(line)
            }
            reader.close()
            val jsonString = responseData.toString()
            // JSONをデシリアライズしてItemクラスのリストに変換
            val items = Json.decodeFromString>(jsonString)
            //リターン
            items
        } else {
            //リターン
            null
        }
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}

動作確認
APIサーバーは稼働している前提とし、アプリケーションを起動して受信した情報が表示されたら成功です。

APIを利用して情報をGET

今回はKotlinでAPIを利用するサンプルプログラムを紹介しました。 APIにリクエスト→APIからのレスポンスを受け取る・・・という処理を流れを理解できましたか?

KotlinでMQTT処理を覚えられましたか?

よく分からないという人は、デバッグしながら少しずつ理解していきましょう! Kotlinに限らず、APIを利用するプログラムに触れる機会は多いので勉強しておいて損はないかと思います。頑張りましょう!

管理人情報