Memo

メモ > 技術 > IDE: AndroidStudio > アプリの作成(Jetpack Compose / リストの登録編集削除)

■アプリの作成(Jetpack Compose / リストの登録編集削除)
#63 Jetpack ComposeでToDoアプリを作る - 概要 | Mokelab Blog https://blog.mokelab.com/63/compose_todo1.html リストの並び替えは、現状対応していないみたい 【Android】 Jetpack Composeでドラッグ&ドロップの並び替えを実現する 〜ライブラリに頼って〜 - Qiita https://qiita.com/tsumuchan/items/d0fc2a4bd4af6802f9fc Sorting List Items in LazyColumn - Android Jetpack Compose - Stack Overflow https://stackoverflow.com/questions/73915584/sorting-list-items-in-lazycolumn-android-jetpack-compos... 以下はデータ追加用に「+」ボタンを表示する例
Scaffold( topBar = { TopAppBar( title = { Text("リストのサンプル") } ) }, floatingActionButton = { FloatingActionButton(onClick = { Log.d("FloatingActionButton", "Clicked!") }) { Icon( imageVector = Icons.Filled.Add, contentDescription = "追加" ) } } ) { innerPadding ->
上記を踏まえて、リストの登録編集削除を作成する ■リストの登録編集削除(調整中) build.gradle
plugins { id 'com.android.application' version '8.0.2' apply false id 'com.android.library' version '8.0.2' apply false id 'org.jetbrains.kotlin.android' version '1.7.20' apply false id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.20' apply false }
app/build.gradle
plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.plugin.serialization' } 〜中略〜 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' implementation "androidx.navigation:navigation-compose:2.5.3" implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2" implementation "androidx.datastore:datastore-preferences:1.0.0" 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' }
java/jp/terraport/list/MainActivity.kt
package jp.terraport.list import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import jp.terraport.list.ui.MainApp import jp.terraport.list.ui.theme.ListTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ListTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { MainApp() } } } } } @Preview(showSystemUi = true) @Composable fun GreetingPreview() { ListTheme { MainApp() } }
java/jp/terraport/list/ui/MainApp.kt
package jp.terraport.list.ui import android.net.Uri import androidx.compose.runtime.Composable import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import jp.terraport.list.ui.add.AddScreen import jp.terraport.list.ui.edit.EditScreen import jp.terraport.list.ui.list.ListScreen @Composable fun MainApp() { val navController = rememberNavController() NavHost(navController = navController, startDestination = "ListScreen") { composable("ListScreen") { ListScreen( onNavigateToAddScreen = { navController.navigate("AddScreen") }, onNavigateToEditScreen = { id, title, body -> navController.navigate("EditScreen?id=" + Uri.encode(id) + "&title=" + Uri.encode(title) + "&body=" + Uri.encode(body)) } ) } composable("AddScreen") { AddScreen( onNavigateToFirstScreen = { navController.navigateUp() } ) } composable( "EditScreen?id={id}&title={title}&body={body}", arguments = listOf( navArgument("id") { type = NavType.StringType }, navArgument("title") { type = NavType.StringType }, navArgument("body") { type = NavType.StringType } ) ) { backStackEntry -> EditScreen( backStackEntry.arguments?.getString("id") ?: "", backStackEntry.arguments?.getString("title") ?: "", backStackEntry.arguments?.getString("body") ?: "", onNavigateToFirstScreen = { navController.navigateUp() } ) } } }
java/jp/terraport/list/ui/list/ListScreen.kt
package jp.terraport.list.ui.list import android.util.Log import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.constraintlayout.compose.ConstraintLayout import jp.terraport.list.utils.Memo import jp.terraport.list.utils.getMemo @OptIn(ExperimentalMaterial3Api::class) @Composable fun ListScreen( onNavigateToAddScreen: () -> Unit, onNavigateToEditScreen: (String, String, String) -> Unit ) { val context = LocalContext.current val memos = remember { mutableStateListOf<Memo>() } LaunchedEffect(Unit) { memos.addAll(getMemo(context)) } Scaffold( topBar = { TopAppBar( title = { Text("リストのサンプル") } ) }, floatingActionButton = { FloatingActionButton(onClick = { onNavigateToAddScreen() }) { Icon( imageVector = Icons.Filled.Add, contentDescription = "追加" ) } } ) { innerPadding -> ConstraintLayout( modifier = Modifier.padding(innerPadding) ) { LazyColumn { items(memos.size) { index -> val content = memos[index] ListTitle(title = content.title, body = content.detail) { onNavigateToEditScreen(content.id, content.title, content.detail) } } } } } }
java/jp/terraport/list/ui/list/ListTitle.kt
package jp.terraport.list.ui.list import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @Composable fun ListTitle( title: String, body: String, onClick: () -> Unit ) { Surface( modifier = Modifier.clickable { onClick() }, shape = MaterialTheme.shapes.medium ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(all = 8.dp) ) { Column( modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp, vertical = 16.dp), ) { Text(title) Spacer(modifier = Modifier.height(4.dp)) Text(body) } } } }
java/jp/terraport/list/ui/add/AddScreen.kt
package jp.terraport.list.ui.add import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import jp.terraport.list.utils.Memo import jp.terraport.list.utils.generateRandomString import jp.terraport.list.utils.getMemo import jp.terraport.list.utils.putMemo import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @OptIn(ExperimentalMaterial3Api::class) @Composable fun AddScreen( onNavigateToFirstScreen: () -> Unit ) { val context = LocalContext.current var titleText by remember { mutableStateOf(TextFieldValue("")) } var detailText by remember { mutableStateOf(TextFieldValue("")) } Scaffold( topBar = { TopAppBar( title = { Text("登録") }, navigationIcon = { IconButton(onClick = onNavigateToFirstScreen) { Icon( imageVector = Icons.Default.ArrowBack, contentDescription = "戻る" ) } } ) } ) { innerPadding -> ConstraintLayout( modifier = Modifier.padding(innerPadding), ) { Column { OutlinedTextField( value = titleText, onValueChange = { titleText = it }, modifier = Modifier.fillMaxWidth() ) Spacer(modifier = Modifier.height(16.dp)) OutlinedTextField( value = detailText, onValueChange = { detailText = it }, modifier = Modifier.fillMaxWidth() ) Spacer(modifier = Modifier.height(16.dp)) Button(onClick = { runBlocking(Dispatchers.IO) { val memos: MutableList<Memo> = mutableListOf() memos.add(Memo(generateRandomString(16), titleText.text, detailText.text)) memos.addAll(getMemo(context)) putMemo(context, memos) } onNavigateToFirstScreen() }) { Text("保存") } } } } }
java/jp/terraport/list/ui/edit/EditScreen.kt
package jp.terraport.list.ui.edit import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import jp.terraport.list.utils.Memo import jp.terraport.list.utils.getMemo import jp.terraport.list.utils.putMemo import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @OptIn(ExperimentalMaterial3Api::class) @Composable fun EditScreen( id: String, title: String, body: String, onNavigateToFirstScreen: () -> Unit ) { val context = LocalContext.current var titleText by remember { mutableStateOf(TextFieldValue(title)) } var detailText by remember { mutableStateOf(TextFieldValue(body)) } Scaffold( topBar = { TopAppBar( title = { Text("詳細") }, navigationIcon = { IconButton(onClick = onNavigateToFirstScreen) { Icon( imageVector = Icons.Default.ArrowBack, contentDescription = "戻る" ) } } ) } ) { innerPadding -> ConstraintLayout( modifier = Modifier.padding(innerPadding), ) { Column { OutlinedTextField( value = titleText, onValueChange = { titleText = it }, modifier = Modifier.fillMaxWidth() ) Spacer(modifier = Modifier.height(16.dp)) OutlinedTextField( value = detailText, onValueChange = { detailText = it }, modifier = Modifier.fillMaxWidth() ) Spacer(modifier = Modifier.height(16.dp)) Button(onClick = { runBlocking(Dispatchers.IO) { val memos: MutableList<Memo> = mutableListOf() getMemo(context).forEach { memo -> if (id == memo.id) { memos.add(Memo(id, titleText.text, detailText.text)) } else { memos.add(Memo(memo.id, memo.title, memo.detail)) } } putMemo(context, memos) } onNavigateToFirstScreen() }) { Text("保存") } Spacer(modifier = Modifier.height(16.dp)) Button(onClick = { runBlocking(Dispatchers.IO) { val memos: MutableList<Memo> = mutableListOf() getMemo(context).forEach { memo -> if (id != memo.id) { memos.add(Memo(memo.id, memo.title, memo.detail)) } } putMemo(context, memos) } onNavigateToFirstScreen() }) { Text("削除") } } } } }
java/jp/terraport/list/utils/Memo.kt
package jp.terraport.list.utils import android.content.Context import android.util.Log import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.Json import java.io.IOException @Serializable data class Memo( val id: String, val title: String, val detail: String ) suspend fun getMemo(context: Context): List<Memo> { return try { Json.decodeFromString(ListSerializer(Memo.serializer()), getData(context, "memo")) } catch (e: Exception) { Log.e("getMemo", "Decode failed.", e) emptyList() } } suspend fun putMemo(context: Context, memos: List<Memo>) { try { putData(context, "memo", Json.encodeToString(ListSerializer(Memo.serializer()), memos)) } catch (e: IOException) { Log.e("putMemo", "Encode Failed.", e) } }
java/jp/terraport/list/utils/Common.kt
package jp.terraport.list.utils import android.content.Context import android.util.Log import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.first import java.io.IOException val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "list") suspend fun getData(context: Context, name: String): String { var data: String? = null try { data = context.dataStore.data.first()[stringPreferencesKey(name)] } catch (e: IOException) { Log.e("getData", "Get failed.", e) } return data ?: "[]" } suspend fun putData(context: Context, name: String, data: String) { try { context.dataStore.edit { entries -> entries[stringPreferencesKey(name)] = data } } catch (e: IOException) { Log.e("putData", "Put failed.", e) } } fun generateRandomString(length: Int): String { val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9') return (1..length) .map { allowedChars.random() } .joinToString("") }

Advertisement