🗺️

Kotlin/Jetpack Composeを使ってHERE SDKに入門する

に公開

はじめに

HERE JapanでHERE SDKの開発をしているmasaroniです。
HEREはGISプラットフォームのプロバイダーとして、位置情報に関する様々なサービスを展開しています。その中でHERE SDKはモバイルアプリ向けにHEREが展開するロケーションサービスを利用するためのインターフェースを提供するプロダクトになります。

かくいう私はモバイルアプリケーションの開発経験があまりありませんでした。
モバイル向けのSDKを開発しているエンジニアがモバイルアプリに疎くてはけしからんということで、このたびモバイルアプリ開発に入門してみることにしました!
せっかくなので、今年からサポートが始まったKotlinのJetpack Composeを使ってHEREの地図を表示させてみたいと思います。

コードは以下のリポジトリで確認することができます。
https://github.com/smallriver0316/HelloHereMapKotlin

ちなみにHEREのサービスやHERE SDKについては公式ドキュメントの他、特にHERE SDK for Flutterについてはこちらの技術書にも詳しく記載されています。

Jetpack Composeとは

Jetpack Composeは、ネイティブUIをビルドする際に推奨されているAndroidアプリの最新UIツールキットです。これまでAndroidアプリのUI構成はXMLレイアウトファイルに記述しなければならなかったため、XMLとJavaまたはKotlinの2種類のファイルを編集しなければなりませんでした。しかし、Jetpack Composeを使えば、UIのコンポーネントをKotlinの関数として実装することができます。
Jetpack Composeの主な利点をまとめると以下のようになります。

  • 宣言的UIにより、状態遷移に基づく動的なUIが実現できる
  • アプリの実装がKotlinで統一され、コードの管理がしやすくなる
  • UIをKotlinの関数として実装できるので、UIコンポーネントの再利用性が向上する
  • 強力な状態管理機能
  • Material Designのサポート

開発環境

  • Android Studio: Narwhal 3 Feature Drop | 2025.1.3
  • Gradle version: 8.13
  • Kotlin: 2.0.21
  • Compose BOM: 2024.09.00
  • Minimum SDK: API24("Nougat"; Android 7.0)
  • HERE SDK for Android Explore: version 4.24.2.0.232174

プロジェクトの作成

まずは、HERE platform portalにアカウントを作っておきます。
アカウントの詳しい作成手順はここでは割愛します。
SDKやAPIへアクセスするための認証情報はここから取得します。

HERE SDKのダウンロード

アカウントが発行され、ログインに成功すると、以下のようなダッシュボードが表示されます。

HERE SDKのダウンロードページに移動します。

HERE SDKにはExploreとNavigationという2つのバージョンがあります。Navigation Editionの方が
高機能で、オフラインマップ機能やリアルタイムナビゲーション機能などが利用できます。
今回は地図を表示するだけなので、「HERE SDK for Android Explore」をダウンロードします。

認証キーの取得

認証情報を取得するには、アプリケーションの登録が必要になります。
画面右上のハンバーガーアイコンをクリックしてランチャーを表示。アクセスマネージャーからアプリの登録画面に遷移できます。

任意のアプリ名を入力して、アプリを登録すると、認証キーを作成できるようになります。
左ペインの「OAuth2.0」を選択し、「資格情報の作成」ボタンをクリックします。
これでアクセスキーIDとシークレットアクセスキーが作成されるので、メモするなり、テキストファイルとしてダウンロードするなりして保存しておきます。

Androidプロジェクトへの統合

Android StudioでEmpty Activityのプロジェクトを作ります。
作成時の設定は次のようになります。

  • Name: 任意のプロジェクト名
  • Minimum SDK: API24("Nougat"; Android 7.0)
  • Build Configuration Language: Kotlin DSK(build.gradle.kts)[Recommended]

作成したプロジェクトにapp/libs/ディレクトリを作成し、そこにダウンロードしたHERE SDKの.aarファイル(heresdk-explore-android-x.xx.x.x.xxxxxx.aar)を配置します。

これをAndroidアプリのビルド時に読みこむようにする必要があります。
app/gradle.build.ktsを以下のコードを追記します。

// build.gradle.kts
dependencies {
    ...
+    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"), "exclude" to listOf("*mock*.jar"))))
    ...
}

これができたらAndroid Studioの右上にある"Sync Project with Gradle Files"ボタンをクリックします。

.aarファイルを無事にSyncできれば、HERE SDKを使う準備は完了です。

SDKNativeEngineの初期化

いよいよKotlinでの実装に取り掛かります。
まずは、HERE SDKのSDKNativeEngineを初期化する必要があります。

Android StudioでMainActivity.kt(<project root>/app/src/main/java/com/example/<project name>/MainActivity.kt)を開き、MainActivityクラスのメンバー関数として、初期化を行う関数を実装します。

// MainActivity.kt
import com.here.sdk.core.engine.AuthenticationMode
import com.here.sdk.core.engine.SDKNativeEngine
import com.here.sdk.core.engine.SDKOptions
import com.here.sdk.core.errors.InstantiationErrorException

...

class MainActivity : ComponentActivity() {
    ...

    private fun initHERESDK() {
        val accessKeyId = <アクセスキーID>
        val accessKeySecret = <シークレットアクセスキー>
        val authenticationMode = AuthenticationMode.withKeySecret(accessKeyId, accessKeySecret)
        val options = SDKOptions(authenticationMode)
        try {
            SDKNativeEngine.makeSharedInstance(this, options)
        } catch (e: InstantiationErrorException) {
            throw RuntimeException("Initialization of HERE SDK failed: " + e.error.name)
        }
    }

事前に取得しておいたアクセスキーはここで使います。
また、HERE SDKの各種クラスの仕様は公式ドキュメントから確認できます。
(Ex: AuthenticationMode, SDKNativeEngine, SDKOptions)

認証情報をハードコーディングするのが気になる場合は、先に認証情報の管理の手順を行うのをお勧めします。

MainActivityクラスは、Androidアプリのアクティビティ・ライフサイクルの各状態に対応するコールバック関数が継承されています。
そのコールバック関数を実装することで、SDKNativeEngineの初期化とリソースの破棄が状態遷移によって実行されるようにします。

先ほど実装したinitHERESDK()メソッドはMainActivityクラスのonCreate()メソッド内で実行します。
プロジェクトを立ち上げた際に、既にonCreate()にはサンプルコードが実装されていますが、その内容を以下のように変更します。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
+        initHERESDK()
        enableEdgeToEdge()
        setContent {

初期化と合わせて終了処理も実装します。
SDKNativeEngineをdisposeしてリソースを開放する関数を実装します。

    private fun disposeHERESDK() {
        SDKNativeEngine.getSharedInstance()?.dispose()
        SDKNativeEngine.setSharedInstance(null)
    }

この関数は、MainActivityクラスのonDestroy()メソッドで呼ばれるようにします。
onDestroy()はサンプルコードにはないので、以下のように実装します。

    override fun onDestroy() {
        super.onDestroy()
        disposeHERESDK()
    }

地図の表示

いよいよ地図の表示部分を実装していきます。
地図の表示はHERE SDKのMapViewクラスで行います。まずは、MapViewクラスのインスタンスをMainActivityクラスにメンバー変数として追加します。

import com.here.sdk.mapview.MapScheme
import com.here.sdk.mapview.MapView

class MainActivity : ComponentActivity() {
    private var mapView: MapView? = null

MapViewインスタンスのセットアップを行う関数を以下のように実装します。引数としてアクティビティの状態を保持するBundleオブジェクトを指定します。

    private fun setupMapView(savedInstanceState: Bundle?, mapView: MapView) {
        this.mapView = mapView
        mapView.onCreate(savedInstanceState)
        mapView.onResume()
        if (savedInstanceState == null) {
            mapView.mapScene.loadScene(MapScheme.NORMAL_DAY, null)
        }
    }

ここでやっとJetpack Composeを使います。先ほど実装したsetupMapView()も使って、地図を表示するComposable関数を以下のように実装します。
元々、HERE SDKはJetpack Compose登場以前に登場したモバイルアプリ向けのSDKなので、そのままではJetpack Composeでの描画に対応できません。それをJetpack Composeで描画するためのインターフェースとしてAndroidViewクラスを用います。

import androidx.compose.ui.viewinterop.AndroidView
...

    @Composable
    fun HereMapView(savedInstanceState: Bundle?) {
        AndroidView(factory = { context ->
            MapView(context).apply {
                setupMapView(savedInstanceState, this)
            }
        })
    }

このHereMapView()関数そのものがUIの描画に利用されます。
onCreate()メソッド内のサンプルコードを削除し、HereMapView()が実行されるように編集します。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initHERESDK()
        enableEdgeToEdge()
        setContent {
            HelloHereMapTheme {
                HereMapView(savedInstanceState)
            }
        }
    }

最後にMapViewインスタンスの終了処理もonDestroy()メソッドに追記します。

    override fun onDestroy() {
        super.onDestroy()
+        mapView?.onDestroy()
        disposeHERESDK()
    }

ここまでできれば、Android Studio上でエミュレータを起動してアプリを実行してみましょう。
以下のように地図画面が表示されれば完成です。
デフォルトの中心地点はHERE Technologiesのベルリン・オフィスとなっています。

日本地図の表示

日本のユーザー向けにアプリを実装するなら、最初に表示される場所も日本にしたいものです。
そこで、地図のデフォルトの中心地点を日本に変更してみます。

作成したsetupMapView()関数に以下のようなコードを追加します。

+ import com.here.sdk.core.GeoCoordinates
+ import com.here.sdk.mapview.MapMeasure

...

    private fun setupMapView(savedInstanceState: Bundle?, mapView: MapView) {
        this.mapView = mapView
        mapView.onCreate(savedInstanceState)

+        // Set Tokyo station as the center of the map
+        val distanceInMeters = (1000 * 10).toDouble()
+        val mapMeasureZoom: MapMeasure = MapMeasure(MapMeasure.Kind.DISTANCE_IN_METERS, distanceInMeters)
+        mapView.camera.lookAt(GeoCoordinates(35.68147498159897, 139.76704300723313), mapMeasureZoom)

        mapView.onResume()

MapViewインスタンスを使った操作はmapView.onCreate()を実行した後でないとエラーがでるので、注意が必要です。

これでアプリ起動時から日本地図が表示されるようになりました。

認証情報の管理

HERE Technologiesが提供するサンプルコードやチュートリアルでは、たいてい認証情報がハードコーディングするようになっているのですが、これをそのままGithubなどにアップしてしまうとセキュリティ・インシデントを招いてしまいます。
そこで、認証情報をローカルにあるファイルに記載しておいて、それをビルド時に読みこめるようにしてみました。

まずは、local.propertiesファイルに認証情報を記載します。このファイルはgit管理しません。

# local.properties
HERE_ACCESS_KEY_ID=<アクセスキーID>
HERE_ACCESS_KEY_SECRET=<シークレットアクセスキー>

ビルド時にlocal.propertiesファイルから認証情報を読みこむように、build.gradle.ktsを編集します。
local.propertiesの読み込みは、JavaのPropertiesクラスを使って以下のように実装できます。

// build.gradle.kts
+ import java.io.FileInputStream
+ import java.util.Properties

...

+ val localProperties = Properties()
+ val localPropertiesFile = rootProject.file("local.properties")
+ if (localPropertiesFile.exists()) {
+     localProperties.load(FileInputStream(localPropertiesFile))
+ }

android {
    ...
    defaultConfig {
        applicationId = "com.example.helloheremap"
        minSdk = 24
        targetSdk = 36
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

+        val hereAccessKeyId = localProperties.getProperty("HERE_ACCESS_KEY_ID", "")
+        val hereAccessKeySecret = localProperties.getProperty("HERE_ACCESS_KEY_SECRET", "")

+        buildConfigField("String", "HERE_ACCESS_KEY_ID", "\"$hereAccessKeyId\"")
+        buildConfigField("String", "HERE_ACCESS_KEY_SECRET", "\"$hereAccessKeySecret\"")
    }
    ...
    buildFeatures {
        compose = true
+        buildConfig = true
    }

buildFeaturesにbuildConfig = trueを指定することで、BuildConfigクラスが生成され、アプリのパッケージ内に配置されます。

最後にMainActivity.ktに戻って、認証情報をハードコーディングしていた部分をBuildConfigクラスのメンバーで置き換えます。

    private fun initHERESDK() {
        val accessKeyId = BuildConfig.HERE_ACCESS_KEY_ID
        val accessKeySecret = BuildConfig.HERE_ACCESS_KEY_SECRET

再度、アプリをビルドして、期待通りの動作を確認できれば完成です。

おわりに

KotlinのJetpack Composeを使ってHEREの地図を表示するチュートリアルは公式ドキュメントにもあるのですが、Javaや通常のKotlinでのチュートリアルと混在しているため、少し分かりづらくなっています。

そこで今回は、Jetpack Composeに着目してより分かりやすくまとめてみました。実際、Jetpack Composeを利用することで、従来より簡潔にアプリを実装できることが確認できました。

次はNavigationの機能を使った開発に挑戦したいと思います。

Discussion