6. Scaffold, TextField, Button, 구조 분해, SnackBar, 코루틴 스코프
위와 같이 TextField에 문자를 입력한 다음 버튼을 누를 시 snackbar가 뜨는 화면을 만들어 보겠다.
먼저 textField를 만든다.
setContent {
val textValue=remember{
mutableStateOf("")
}
Column(
modifier=Modifier.fillMaxSize(),
verticalArrangement= Arrangement.Center,
horizontalAlignment= Alignment.CenterHorizontally,
){
TextField(
value=textValue.value,
onValueChange={
textValue.value=it
},
)
}
}
위 코드에서 구조 분해를 이용할 경우 아래와 같이 수정한다.
//수정 전
val textValue=remember{
mutableStateOf("")
}
TextField(
value=textValue.value,
onValueChange={
textValue.value=it
},
)
//수정 후
val (text,setValue)=remember{
mutableStateOf("")
}
TextField(
value=text,
onValueChange=setValue
)
다음은 Snackbar를 이용하기 위해 Scaffold와 버튼을 추가한다.
- Scaffold: 앱의 기본 레이아웃을 정의하고, 앱 바 (AppBar), 하단 내비게이션 바, 드로어 (Drawer), 스낵바 (Snackbar) 및 기타 구성 요소를 관리하기 위한 도구를 제공한다.
- SnackBar: 사용자에게 간단한 메시지를 표시하거나 사용자 작업을 알리는 데 사용되는 경고 또는 알림 메시지를 나타내는 UI 요소
이때 코루틴을 사용한다.
- 코루틴 : 코루틴은 비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴
- 코루틴 스코프: 코루틴의 실행 수명 주기와 범위를 정의하는 데 사용된다.
setContent {
//구조 분해 사용
val (text,setValue)=remember{
mutableStateOf("")
}
val scaffoldState = rememberScaffoldState()
val scope=rememberCoroutineScope() //코루틴 스코프
val keyboardController=LocalSoftwareKeyboardController.current
Scaffold(
scaffoldState = scaffoldState,
) {
Column(
modifier=Modifier.fillMaxSize(),
verticalArrangement= Arrangement.Center,
horizontalAlignment= Alignment.CenterHorizontally,
){
TextField(
value=text,
onValueChange=setValue,
)
Button(onClick={
keyboardController?.hide() //키보드 숨기기
scope.launch{
scaffoldState.snackbarHostState.showSnackbar("Hello $text")
}
}){
Text("클릭")
}
}
}
}
7. Navigation
위와 같이 버튼을 눌렀을 때 다른 화면으로 이동하고 이동할 때 이전 화면에 입력한 글자를 다음 화면에 보이도록 만들어보겠다.
우선 navigation을 사용하기 위해서 dependencies에 아래 코드를 추가한다.
def nav_version = "2.5.3"
implementation "androidx.navigation:navigation-compose:$nav_version"
Activity에 아래와 같은 코드를 작성한다.
setContent {
val navController=rememberNavController()
NavHost(
navController=navController,
startDestination="first",
){
composable("first"){
FirstScreen(navController)
}
composable("second"){
SecondScreen(navController)
}
composable("third/{value}"){ backStackEntry->
ThirdScreen(
navController=navController,
value=backStackEntry.arguments?.getString("value") ?:""
)
}
}
}
이제 각 화면 별 Compose를 만든다. 첫 번째 화면에서 "두 번째" 버튼 클릭 시 다음 화면으로 넘어갈 수 있도록 FirstScreen을 다음과 같이 작성한다.
@Composable
fun FirstScreen(navController: NavController){
Column(
//중략
){
//중략
Button(onClick={navController.navigate("second")}){Text("두 번째")}
//중략
}
}
두 번째 화면에서 "뒤로 가기" 버튼을 누를 때 다시 첫 번째 화면으로 돌아가려면 SecondScreen을 아래와 같은 코드를 작성한다.
@Composable
fun SecondScreen(navController: NavController){
Column(
//중략
){
//중략
Button(onClick={navController.navigateUp()}) {Text("뒤로 가기")}
}
}
이때 navController.navigateUp() 대신 navController.popBackStack()을 이용해도 된다.
이번에는 첫 번째 화면에서 글자를 입력한 후 "세 번째" 버튼을 클릭 시 세 번째 화면에 글자가 보이도록 하기 위해서 FirstScreen과 ThirdScreen을 다음과 같이 작성한다. 세 번째 화면에서도 "뒤로 가기" 버튼을 누르면 첫 번째 화면으로 돌아가도록 코드를 작성한다.
fun FirstScreen(navController: NavController){
val(value,setValue)=remember{
mutableStateOf("")
}
Column(
//중략
){
//중략
TextField(value=value,onValueChange=setValue)
Button(onClick={
if(value.isNotEmpty()){
navController.navigate("third/$value")
}
}){Text("세 번째")}
}
}
@Composable
fun ThirdScreen(navController: NavController,value:String){
Column(
//중략
){
//중략
Text(value)
Button(onClick={
navController.navigateUp()
}) {Text("뒤로 가기")}
}
}
전체 코드는 아래와 같다.
class SeventhActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController=rememberNavController()
NavHost(
navController=navController,
startDestination="first",
){
composable("first"){
FirstScreen(navController)
}
composable("second"){
SecondScreen(navController)
}
composable("third/{value}"){ backStackEntry->
ThirdScreen(
navController=navController,
value=backStackEntry.arguments?.getString("value") ?:""
)
}
}
}
}
}
@Composable
fun FirstScreen(navController: NavController){
val(value,setValue)=remember{
mutableStateOf("")
}
Column(
modifier=Modifier.fillMaxSize(),
verticalArrangement=Arrangement.Center,
horizontalAlignment= Alignment.CenterHorizontally,
){
Text(text="첫 번째 화면")
Spacer(modifier=Modifier.height(16.dp))
Button(onClick={
navController.navigate("second")
}){
Text("두 번째")
}
Spacer(modifier=Modifier.height(16.dp))
TextField(value=value,onValueChange=setValue)
Button(onClick={
if(value.isNotEmpty()){
navController.navigate("third/$value")
}
}){
Text("세 번째")
}
}
}
@Composable
fun SecondScreen(navController: NavController){
Column(
modifier=Modifier.fillMaxSize(),
verticalArrangement=Arrangement.Center,
horizontalAlignment= Alignment.CenterHorizontally,
){
Text(text="두 번째 화면")
Spacer(modifier=Modifier.height(16.dp))
Button(onClick={
navController.navigateUp()
}) {
Text("뒤로 가기")
}
}
}
@Composable
fun ThirdScreen(navController: NavController,value:String){
Column(
modifier=Modifier.fillMaxSize(),
verticalArrangement=Arrangement.Center,
horizontalAlignment= Alignment.CenterHorizontally,
){
Text(text="세 번째 화면")
Spacer(modifier=Modifier.height(16.dp))
Text(value)
Button(onClick={
navController.navigateUp()
}) {
Text("뒤로 가기")
}
}
}
8. ViewModel
ViewModel 클래스는 수명 주기를 고려하여 UI 관련 데이터를 저장하고 관리하도록 설계되었다. ViewModel 클래스를 사용하면 화면 회전과 같이 구성을 변경할 때도 데이터를 유지할 수 있다.
위와 같이 버튼을 클릭했을 때 텍스트가 바뀌는 화면을 만들어보겠다.
setContent {
val data=mutableStateOf("Hello")
Column(
modifier=Modifier.fillMaxSize(),
verticalArrangement= Arrangement.Center,
horizontalAlignment= Alignment.CenterHorizontally,
){
Text(
data.value,
fontSize=30.sp,
)
Button(onClick={
data.value="World"
}){
Text("변경")
}
}
}
우선 위와 같이 코드를 만들었을 때 버튼을 클릭했을 때 텍스트 값이 바뀌지 않는다. 왜냐하면 버튼을 클릭 시 리컴포지션이 일어나 data의 value 값이 다시 "Hello"가 되게 하기 때문이다.
이때 remember를 사용한다. remember는 현재 컴포넌트가 다시 그려질 때 상태를 보존하는 방법이다. 이것은 앱이 화면 방향 변경 또는 구성 변경과 같은 이벤트로부터 데이터를 보존할 수 있게 한다. remember 함수를 사용하여 상태를 보존하면 버튼을 눌렀을 때 텍스트가 변경되고 화면이 업데이트된다.
val data=remember{mutableStateOf("Hello")}
이번에는 ViewModel을 사용해서 화면을 만들어 보겠다. 아래와 같이 ViewModel을 상속받은 클래스를 생성한다.
class MainViewModel: ViewModel(){
val data=mutableStateOf("Hello")
}
ViewModel을 사용할 때는 remember를 사용하지 않아도 된다. 아래 코드를 Activity 안에 추가한다.
private val viewModel by viewModels<MainViewModel>()
그러면 ViewModel을 통해 data 값을 바꿀 수 있게 된다.
Text(
viewModel.data.value,
fontSize=30.sp,
)
Button(onClick={
viewModel.data.value="World"
}){
Text("변경")
}
Compose 안에서 ViewModel을 사용하려면 dependencies에 아래 코드를 추가해 주고
def lifecycle_version = "2.5.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
SetContent 안에 아래 코드를 넣어준다.
val viewModel = viewModel<MainViewModel>()
추가로 ViewModel을 이용할 때 외부에 데이터를 공개하지 않는다. 따라서 아래 코드처럼 view에서 데이터 값을 조작하지 않는 것이 좋다.
viewModel.data.value="World"
따라서 데이터를 외부에 공개하지 않고 읽기 전용 변수(_data)를 만들어준다.
class MainViewModel: ViewModel(){
private val _data = mutableStateOf("Hello")
val data:State<String> = _data
}
데이터 값을 바꾸고 싶다면 class 안에 함수를 따로 만들어준다.
fun changeValue(){
_data.value="world"
}
따라서 텍스트 값 변경과 관련된 코드를 아래와 같이 바꿔준다.
Button(onClick={
viewModel.changeValue()
}){Text("변경")}
9. State 심화
State는 시간이 지남에 따라 변할 수 있는 값을 의미한다. 이는 매우 광범위한 정의로서 Room 데이터베이스부터 클래스 변수까지 모든 항목이 포함된다.
mutableStateOf는 관찰 가능한 MutableState<T>를 생성하는데, 이는 런타임 시 Compose에 통합되는 관찰 가능한 유형이다. value가 변경되면 value를 읽는 구성 가능한 함수의 리컴포지션이 예약된다. MutableState 내부 구조는 아래와 같다.
interface MutableState<T> : State<T> {
override var value: T
operator fun component1(): T
operator fun component2(): (T) -> Unit
}
컴포저블에서 MutableState 객체를 선언하는 데는 세 가지 방법이 있다.
//text1은 MutableState<String> 타입
val text1= remember{
mutableStateOf("Hello world")
}
//text2는 String 타입
var text2 by remember{
mutableStateOf("Hello World")
}
//text는 String 타입, setText는 (String)->Unit
val(text,setText)=remember{
mutableStateOf("Hello world")
}
값을 수정할 때는 아래처럼 코드를 작성한다.
text1.value="변경"
text2="변경"
setText("변경")
참고자료
https://www.youtube.com/playlist?list=PLxTmPHxRH3VV8lJq8WSlBAhmV52O2Lu7n: 6~9강
https://developer.android.com/jetpack/compose/state?hl=ko
'Group Study (2023-2024) > Android 심화' 카테고리의 다른 글
[Android 심화] 5주차 스터디 - 의존성 주입(DI), Hilt, Room (0) | 2023.12.20 |
---|---|
[Android 심화] 4주차 스터디 - AAC ViewModel (1) | 2023.11.27 |
[Android 심화] 3주차 스터디 - Movie App 만들기(Scaffold, LazyColumn, Navigation) (1) | 2023.11.20 |
[Android 심화] 1주차 스터디 - Jetpack Compose 입문(1) (1) | 2023.11.06 |