NCMB Kotlin SDKを使ってTodoアプリを作る（その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を使ってTodoアプリを作ってみます。前回は[画面の仕様とSDKの初期化について解説しました](https://qiita.com/goofmint/items/8b0b566ad213a6215d94)ので、今回はタスクデータの保存について解説します。

## 完成版のコード

作成したデモアプリのコードは[NCMBMania/Kotlin_Todo: Kotlin SDKを使ったTodoアプリです](https://github.com/NCMBMania/Kotlin_Todo)にアップロードしてあります。

## ナビゲーションの振り分け

今回は以下のようなナビゲーションになっています。文字列からNCMBObjectを復元するstrToNCMBObjectについては、[KotlinのNavigation コンポーネントを使ってNCMBObjectを別画面に渡す - Qiita](https://qiita.com/goofmint/items/a0cd211fbcaafb375352#strtoncmbobject%E3%81%AE%E5%87%A6%E7%90%86)を参照してください。

```kotlin
@Composable
fun Navigation() {
    val startDestination = "list"
    val navController = rememberNavController()
    val className = "Task"
    NavHost(navController = navController, startDestination = startDestination) {
        composable(route = "form") {
            val obj = NCMBObject(className)
            FormScreen(navController, obj)
        }
        composable(route = "list") {
            ListScreen(navController = navController)
        }
        composable(
            route = "detail/task={task}",
            arguments = listOf(navArgument("task") { type = NavType.StringType})
        ) { backStackEntry ->
            val obj = strToNCMBObject(backStackEntry.arguments!!.getString("task")!!, className)
            DetailScreen(navController, obj)
        }
        composable(
            route = "edit/task={task}",
            arguments = listOf(navArgument("task") { type = NavType.StringType})
        ) { backStackEntry ->
            val obj = strToNCMBObject(backStackEntry.arguments!!.getString("task")!!, className)
            FormScreen(navController, obj)
        }
    }
}
```

## タスクの登録

タスクの登録と編集は `FormScreen` にて行います。ナビゲーションから新規登録の場合は空のNCMBObject（クラス名はTask）、編集時には対象となるNCMBObjectを受け取ります。 `objectId` の有無によって、タスクのタイトルと本文のデフォルト値を変えています。

```kotlin
fun FormScreen(navController: NavController, obj: NCMBObject) {
    val objectId = obj.getObjectId()
		// タスクのタイトルと本文用
    var title by remember { mutableStateOf(if (objectId != null) obj.getString("title")!! else "") }
    var body by remember { mutableStateOf(if (objectId != null) obj.getString("body")!! else "") }
		// UIのヘッダーラベル
    val header = if (title == "") "新規作成" else "編集"
		// 保存処理中に立てるフラグ
    var progress by remember { mutableStateOf(false) }
		// アラートを表示する際のフラグ
    var showDialog by remember { mutableStateOf(false) }
		// アラートのメッセージ
    var message by remember { mutableStateOf("") }
		// アラート表示用
    if (showDialog) {
        AlertDialog(
            onDismissRequest = {},
            buttons = {
                Button(onClick = {
                    showDialog = false
                }) {
                    Text("OK")
                }
            },
            title = { Text("タスク保存") },
            text = { Text(message) }
        )
    }
		// 保存処理（後述）
    val save = {
    }
    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text("タスクの${header}")
                },
                actions = {
                    IconButton(
                        onClick = {
                        }
                    ){
                        Icon(Icons.Filled.Save, contentDescription = "Save")
                    }
                }
            )
        },
        content = {
            Column {
                OutlinedTextField(
                    value = title,
                    onValueChange = {
                        Log.d("INFO", it)
                        title = it
                                    },
                    modifier = Modifier.padding(20.dp),
                    label = { Text("タスクのタイトル") },
                    singleLine = true
                )
                OutlinedTextField(
                    value = body,
                    onValueChange = { body = it },
                    modifier = Modifier.padding(20.dp).height(150.dp),
                    label = { Text("タスクの本文") },
                    )
                Button(onClick = save,
                    enabled = !progress,
                    modifier = Modifier.padding(20.dp)
                ) {
                    if (progress) {
                        CircularProgressIndicator(
                            strokeWidth = 2.dp,
                            modifier = Modifier.size(24.dp)
                        )
                    } else {
                        Text("保存する")
                    }
                }
            }
        }
    )
}
```

![Screenshot_20220414_155917.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/4e386dcd-2eab-adff-a91e-9e93a7c4a6e8.png)


### 保存処理

保存処理は以下のようになります。 `progress` のフラグを立てて、二重処理を防止しています。入力されたタスクのタイトルと本文を設定し、保存を行います。保存後はナビゲーションで一覧へ戻ります。

```kotlin
val save = {
		progress = true
		try {
				// 入力値の設定
				obj.put("title", title)
				obj.put("body", body)
				// 保存
				obj.save()
				// 一覧画面に遷移
				navController.navigate("list")
		} catch(e: NCMBException){
				message = "タスクが保存できませんでした"
				showDialog = true
		}
		progress = false
}
```

## まとめ

今回はタスクデータの保存について解説しました。次回はタスクデータの検索と一覧表示について解説します。