メモ > 技術 > 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/net/refirio/list/MainActivity.kt
package net.refirio.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 net.refirio.list.ui.MainApp
import net.refirio.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/net/refirio/list/ui/MainApp.kt
package net.refirio.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 net.refirio.list.ui.add.AddScreen
import net.refirio.list.ui.edit.EditScreen
import net.refirio.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/net/refirio/list/ui/list/ListScreen.kt
package net.refirio.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 net.refirio.list.utils.Memo
import net.refirio.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/net/refirio/list/ui/list/ListTitle.kt
package net.refirio.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/net/refirio/list/ui/add/AddScreen.kt
package net.refirio.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 net.refirio.list.utils.Memo
import net.refirio.list.utils.generateRandomString
import net.refirio.list.utils.getMemo
import net.refirio.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/net/refirio/list/ui/edit/EditScreen.kt
package net.refirio.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 net.refirio.list.utils.Memo
import net.refirio.list.utils.getMemo
import net.refirio.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/net/refirio/list/utils/Memo.kt
package net.refirio.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/net/refirio/list/utils/Common.kt
package net.refirio.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("")
}