NCMBのKotlin SDKを使って地図検索アプリを作る（その2：位置情報をインポートする）

NCMBでは公式SDKとしてSwift/Objective-C/Kotlin/Java/Unity/JavaScript SDKを用意しています。また、それ以外にもコミュニティSDKとして、非公式ながらFlutter/React Native/Google Apps Script/C#/Ruby/Python/PHPなど幅広い言語向けにSDKが開発されています。

今回は公式SDKの一つ、Kotlin SDKを使って地図検索アプリを作ってみます。[前回は画面の仕様とSDKの初期化について解説しました](https://qiita.com/goofmint/items/6eaa3cc441fb38c66909)。今回は位置情報をデータストアにインポートする流れを解説します。

## 完成版のコード

作成したデモアプリのコードは[NCMBMania/kotlin-map-search](https://github.com/NCMBMania/kotlin-map-search)にアップロードしてあります。

## インポート画面について

インポート画面はMapBottomNavigationのタブで読み込まれています。 `ImportScreen` がそうです。

```kotlin
NavHost(navController = navController, startDestination = "Map") {
    composable("Map") { MapScreen()}
    composable("Import") { ImportScreen()}
}
```

## インポート画面の構築

まず `ImportScreen` では文字列の配列が入る logs を `mutableStateListOf` で用意します。

```kotlin
@Composable
fun ImportScreen() {
    // ログメッセージ用
    var logs = remember { mutableStateListOf<String>() }
```

UIは次のようになります。インポート実行タンをタップすると、既存データの削除と駅情報を追加します。

```kotlin
Column(
    modifier = Modifier.fillMaxSize(),
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Spacer(Modifier.size(20.dp))
    Button(
        onClick = {
            // 既存データの削除
            deleteAllStations()
            // JSONに入っている駅情報をNCMBのデータストアに保存
            for (i in 0 until stationsJson.length()) {
                val str = saveStation(stationsJson.getJSONObject(i))
                logs.add(str)
            }
        }
    ){
        Text(text = "山手線データをインポートする")
    }
    LazyColumn(
    ) {
        items(logs) { str ->
            Text(text = str)
        }
    }
}
```

## データのインポートについて

データをインポートする際には繰り返し処理できるように、まず既存データを削除しています。もし既存データがなかった場合でも（データストアのクラスがなくとも）エラーにはならず、空の配列が返ってくるだけです。

```kotlin
// 既存データの削除
deleteAllStations()
// JSONに入っている駅情報をNCMBのデータストアに保存
for (i in 0 until stationsJson.length()) {
    val str = saveAllStations(stationsJson.getJSONObject(i))
    logs.add(str)
}

```

deleteAllStations は以下のようになります。NCMBQueryを使ってデータを取得し、各データ（NCMBObject）のdeleteInBackgroundメソッドを使ってデータを削除します。 `delete` もありますが、同期処理なので時間がかかってしまいます。

```kotlin
// 既存のデータを削除する処理
fun deleteAllStations() {
    // 検索対象のNCMBのデータストアクラス
    val query = NCMBQuery.forObject("Station")
    query.limit = 1000 // マックスまで指定しておく
    // 検索は同期で
    val stations = query.find()
    // 削除は非同期で終わらせる
    stations.forEach{
        it.deleteInBackground(NCMBCallback { e, _ ->
        })
    }
}
```

データを削除したら、山手線の各駅の位置情報が入ったJSONファイルの内容をデータストアに反映します。

```kotlin
// JSONに入っている駅情報をNCMBのデータストアに保存
for (i in 0 until stationsJson.length()) {
    val str = saveStation(stationsJson.getJSONObject(i))
    logs.add(str)
}
```

この時利用しているJSONは下記のようになっています。

```
struct Station {
    var name: String
    var latitude: Double
    var longitude: Double
}
```

読み込んだデータをデータストアに登録する `registerDataStore` 関数は以下のようになります。JSONファイルのパラメータを渡して、Stationクラスにデータを作成します。NCMBGeoPointを使って位置情報を保存するのがポイントです。こちらも保存を非同期にして高速化させています。

```kotlin
// 駅情報を保存する処理
fun saveStation(json: JSONObject ): String {
    // 位置情報からNCMBGeoPointを作成
    val geo = NCMBGeoPoint(latitude = json.getDouble("latitude"), longitude = json.getDouble("longitude"))
    // データストアのインスタンスを作成
    val station = NCMBObject("Station")
    // 駅名と位置情報をセット
    station.put("geo", geo)
    station.put("name", json.getString("name"))
    // 保存（量が多いので非同期で）
    station.saveInBackground(NCMBCallback {e, _ ->

    })
    return "${station.getString("name")}を取り込みました"
}
```

## 全体のコード

ImportScreenの内容は次のようになります。

```kotlin
@Composable
fun ImportScreen() {
    // ログメッセージ用
    var logs = remember { mutableStateListOf<String>() }

    // 山手線のJSONデータを読み込む処理
    val context = LocalContext.current
    var inputStream = context.assets.open("yamanote.json")
    val json = BufferedReader(InputStreamReader(inputStream)).readText()
    val stationsJson = JSONArray(json)

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Spacer(Modifier.size(20.dp))
        Button(
            onClick = {
                // 既存データの削除
                deleteAllStations()
                // JSONに入っている駅情報をNCMBのデータストアに保存
                for (i in 0 until stationsJson.length()) {
                    val str = saveStation(stationsJson.getJSONObject(i))
                    logs.add(str)
                }
            }
        ){
            Text(text = "山手線データをインポートする")
        }
        LazyColumn(
        ) {
            items(logs) { str ->
                Text(text = str)
            }
        }
    }
}

// 既存のデータを削除する処理
fun deleteAllStations() {
    // 検索対象のNCMBのデータストアクラス
    val query = NCMBQuery.forObject("Station")
    query.limit = 1000 // マックスまで指定しておく
    // 検索は同期で
    val stations = query.find()
    // 削除は非同期で終わらせる
    stations.forEach{
        it.deleteInBackground(NCMBCallback { e, _ ->
        })
    }
}

// 駅情報を保存する処理
fun saveStation(json: JSONObject ): String {
    // 位置情報からNCMBGeoPointを作成
    val geo = NCMBGeoPoint(latitude = json.getDouble("latitude"), longitude = json.getDouble("longitude"))
    // データストアのインスタンスを作成
    val station = NCMBObject("Station")
    // 駅名と位置情報をセット
    station.put("geo", geo)
    station.put("name", json.getString("name"))
    // 保存（量が多いので非同期で）
    station.saveInBackground(NCMBCallback {e, _ ->

    })
    return "${station.getString("name")}を取り込みました"
}
```

## まとめ

今回はJSONファイルをインポートし、それを使ってNCMBObjectを作成しました。[次回は地図コンポーネントと合わせて、位置情報検索を実装](https://qiita.com/goofmint/items/82662ab5173ba12bfb25)します。