Compose - LiveData

FLUORITE·2025년 1월 21일

안드로이드

목록 보기
19/32

package kr.co.fastcampus.part1.chapter5_3

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import kr.co.fastcampus.part1.chapter5_3.ui.theme.LiveDataTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LiveDataTheme() {
                Surface(
                    modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background
                ) {
                    TopLevel()
                }
            }
        }
    }
}

class ToDoViewModel : ViewModel() {
    // 단계 2: text 상태를 라이브 데이터로 변경합니다.
    // 사용하는 측에서는 `text.observeAsState()`로 구독하세요.
    private val _text = MutableLiveData("")
    val text: LiveData<String> = _text
    val setText: (String) -> Unit  = {
        _text.value = it
    }

    // 단계 3: toDoList 상태를 라이브 데이터로 변경합니다.
    // 모든 연산에서 List를 새로 만들어 라이브 데이터로 전달해야 합니다!!!
    // (초 비추!!)
//    val toDoList = mutableStateListOf<ToDoData>()
    private val _rawToDoList = mutableStateListOf<ToDoData>()
    private val _toDoList = MutableLiveData<List<ToDoData>>(this._rawToDoList)
    val toDoList:LiveData<List<ToDoData>> = _toDoList
    // mutableStateListOf 추가, 삭제, 대입 -> UI 업데이트가 됨. 각 항목의 필드가 바뀌었을 때 갱신이 안되는 문제.
    // LiveData<List<X>>.observeAsState() - List가 통째로 다른 List로 바뀌었을 때만 State가 갱신된다.

    val onSubmit: (String) -> Unit = {
        val key = (this._rawToDoList.lastOrNull()?.key ?: 0) + 1
        this._rawToDoList.add(ToDoData(key, it))
        _toDoList.value = ArrayList(this._rawToDoList)
        _text.value = ""
    }

    val onEdit: (Int, String) -> Unit = { key, newText ->
        val i = this._rawToDoList.indexOfFirst { it.key == key }
        this._rawToDoList[i] = this._rawToDoList[i].copy(text = newText)
        _toDoList.value = ArrayList(this._rawToDoList)
    }

    val onToggle: (Int, Boolean) -> Unit = { key, checked ->
        val i = this._rawToDoList.indexOfFirst { it.key == key }
        this._rawToDoList[i] = this._rawToDoList[i].copy(done = checked)
        _toDoList.value = ArrayList(this._rawToDoList)
    }

    val onDelete: (Int) -> Unit = { key ->
        val i = this._rawToDoList.indexOfFirst { it.key == key }
        this._rawToDoList.removeAt(i)
        _toDoList.value = ArrayList(this._rawToDoList)
    }
}

@Composable
fun TopLevel(viewModel: ToDoViewModel = viewModel()) {
    Scaffold {
        Column {
            ToDoInput(
                text = viewModel.text.observeAsState("").value, onTextChange = {newText ->
                    viewModel.setText(newText)
                }, onSubmit = viewModel.onSubmit
            )
            val items = viewModel.toDoList.observeAsState(emptyList()).value
            LazyColumn {
                items(items = items, key = { it.key }) { toDoData ->
                    ToDo(
                        toDoData = toDoData,
                        onEdit = viewModel.onEdit,
                        onToggle = viewModel.onToggle,
                        onDelete = viewModel.onDelete
                    )
                }
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    LiveDataTheme {
        TopLevel()
    }
}

@Composable
fun ToDoInput(
    text: String, onTextChange: (String) -> Unit, onSubmit: (String) -> Unit
) {
    Row(modifier = Modifier.padding(8.dp)) {
        OutlinedTextField(
            value = text, onValueChange = onTextChange, modifier = Modifier.weight(1f)
        )
        Spacer(modifier = Modifier.size(8.dp))
        Button(onClick = {
            onSubmit(text)
        }) {
            Text("입력")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun ToDoInputPreview() {
    LiveDataTheme {
        ToDoInput("테스트", {}, {})
    }
}

@Composable
fun ToDo(
    toDoData: ToDoData,
    onEdit: (key: Int, text: String) -> Unit = { _, _ -> },
    onToggle: (key: Int, checked: Boolean) -> Unit = { _, _ -> },
    onDelete: (key: Int) -> Unit = {}
) {
    var isEditing by remember { mutableStateOf(false) }
    Card(
        modifier = Modifier.padding(4.dp), elevation = 8.dp
    ) {
        Crossfade(
            targetState = isEditing,
        ) {
            when (it) {
                false -> {
                    Row(
                        modifier = Modifier.padding(8.dp),
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        Text(
                            text = toDoData.text, modifier = Modifier.weight(1f)
                        )
                        Text("완료")
                        Checkbox(checked = toDoData.done, onCheckedChange = { checked ->
                            onToggle(toDoData.key, checked)
                        })
                        Button(onClick = { isEditing = true }) {
                            Text("수정")
                        }
                        Spacer(modifier = Modifier.size(4.dp))
                        Button(onClick = { onDelete(toDoData.key) }) {
                            Text("삭제")
                        }
                    }
                }
                true -> {
                    Row(
                        modifier = Modifier.padding(8.dp),
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        val (text, setText) = remember { mutableStateOf(toDoData.text) }
                        OutlinedTextField(
                            value = text, onValueChange = setText, modifier = Modifier.weight(1f)
                        )
                        Spacer(modifier = Modifier.size(8.dp))
                        Button(onClick = {
                            isEditing = false
                            onEdit(toDoData.key, text)
                        }) {
                            Text("완료")
                        }
                    }
                }
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun ToDoPreview() {
    LiveDataTheme {
        ToDo(ToDoData(1, "nice", true))
    }
}

data class ToDoData(
    val key: Int, val text: String, val done: Boolean = false
)
profile
모두 이루어져라

0개의 댓글