ViewModel 튜토리얼

손현수·2024년 4월 1일

안드로이드 Compose

목록 보기
17/25

섭씨와 화씨 온도를 변환하는 간단한 앱을 구현한다. 온도를 입력하고 버튼을 클릭하면, 변환된 온도가 Text 컴포넌트에 표시된다. Switch 컴포넌트를 이용해 입력되는 온도가 섭씨 온도인지 화씨 온도인지 선택한다. 현재 스위치 설정, 변환 결과, 변환 로직은 ViewModel 안에 포함된다.

ViewModel 추가하기

ViewModel은 상태 변수들을 포함해야 하며, 변환 결과와 현재 스위치 위치가 저장된다. 이 클래스는 모델에 관한 로직도 포함해야 하며, 사용자가 텍스트 필드에 입력한 값을 함수에 전달하고 문자열 -> 정수 변환과 입력값의 유효성도 검사해야 한다. 또한 스위치 설정을 변경하는 함수가 필요하다. 구현 코드는 다음과 같다.

class DemoViewModel: ViewModel() {
    var isFahrenheit by mutableStateOf(true)
    var result by mutableStateOf("")

    fun convertTemp(temp: String) {
        result = try {
            val tempInt = temp.toInt()

            if (isFahrenheit) {
                ((tempInt - 32) * 0.5556).roundToInt().toString()
            } else {
                ((tempInt * 1.8) + 32).roundToInt().toString()
            }
        } catch(e: Exception) {
            "Invalid Entry"
        }
    }
    
    fun switchChange() {
        isFahrenheit = !isFahrenheit
    }
}

Activity에서 DemoViewModel에 접근하기

뷰모델 클래스의 인스턴스를 만들고 ScreenSetup를 통해 상태 변수와 함수 참조를 VMDemoScreen 함수로 전달한다.

@Composable
fun ScreenSetup(viewModel: DemoViewModel = DemoViewModel()) {
    VMDemoMainScreen(
        isFahrenheit = viewModel.isFahrenheit,
        result = viewModel.result,
        convertTemp = {viewModel.convertTemp(it)},
        switchChange = {viewModel.switchChange()}
    )
}

@Composable
fun VMDemoMainScreen(
    isFahrenheit: Boolean,
    result: String,
    convertTemp: (String) -> Unit,
    switchChange: () -> Unit
) {

}

위의 코드에서 convertTemp = {viewModel.convertTemp(it)} it의 역할은 암시적인 파라미터로, 람다식 내부에서는 파라미터가 하나만 존재할 때 it을 사용하여 해당 인자에 접근할 수 있다. it을 사용하지 않은 코드는 다음과 같다.

convertTemp = { temperature ->
	viewModel.convertTemp(temperature)
}

온도 입력 컴포저블 구현하기

VMDemoMainScreen 함수의 기능이 복잡해지는 것을 막기 위해 Switch, OutlinedTextField, 단위 표시 Text 컴포넌트는 InputRow라는 하나의 분리된 컴포넌트에 배치된다.

@Composable
fun InputRow(
    isFahrenheit: Boolean,
    textState: String,
    onTextChange: (String) -> Unit,
    switchChange: () -> Unit
) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Switch(
            checked = isFahrenheit,
            onCheckedChange = { switchChange() }
        )

        OutlinedTextField(
            value = textState,
            onValueChange = { onTextChange(it) },
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Number
            ),
            singleLine = true,
            label = { Text("Enter temperature") },
            modifier = Modifier.padding(10.dp),
            textStyle = TextStyle(
                fontWeight = FontWeight.Bold,
                fontSize = 30.sp
            ),
            trailingIcon = {
                Icon(
                    painter = painterResource(R.drawable.ic_android_black_24dp),
                    contentDescription = "frost",
                    modifier = Modifier.size(40.dp)
                )
            }
        )

        Crossfade(
            targetState = isFahrenheit,
            animationSpec = tween(2000)
        ) { visible ->
            when(visible) {
                true -> Text("\u2109", style = MaterialTheme.typography.headlineSmall)
                false -> Text("\u2103", style = MaterialTheme.typography.headlineSmall)
            }
        }
    }
}

InputRow 함수는 textState 상태 변수 및 onTextChange 이벤트 핸들러와 함께 뷰 모델에 포함된 상탯값과 함수를 파라미터로 받는다. 파라미터로 전달된 상탯값이 변경되면 이를 VMDemoMainScreen으로 들어올린다.

OutlinedTextField에 적용된 keyboardOptions로 인해 사용자가 텍스트 필드를 클릭하면 숫자 키보드가 표시된다.

TextField의 singleLine 설정으로 인해 입력값이 한줄로 표현된다. 이 뿐만 아니라, TextStyle에서는 색상, 배경, 글꼴 종류, 그림자, 텍스트 정렬, 자간, 들여쓰기 등도 설정할 수 있다.
마지막으로 trailingIcon 프로퍼티를 사용해 텍스트 입력 영역 끝에 원하는 아이콘을 위치시킬 수 있다.

VMDemoMainScreen() 완성하기

@Composable
fun VMDemoMainScreen(
    isFahrenheit: Boolean,
    result: String,
    convertTemp: (String) -> Unit,
    switchChange: () -> Unit
) {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        var textState by remember {
            mutableStateOf("")
        }

        val onTextChange = { text: String ->
            textState = text
        }

        Text(
            "Temperature Converter",
            modifier = Modifier.padding(20.dp),
            style = MaterialTheme.typography.headlineSmall
        )

        InputRow(
            isFahrenheit = isFahrenheit,
            textState = textState,
            switchChange = switchChange,
            onTextChange = onTextChange
        )

        Text(
            text = result,
            modifier = Modifier.padding(20.dp),
            style = MaterialTheme.typography.headlineMedium
        )

        Button(
            onClick = { convertTemp(textState) }
        ) {
            Text("Convert Temperature")
        }
    }
}

VMDemoMainscreen()에서는 textState 상태 변수와 onTextChange 이벤트 핸들러를 선언한다. 지금까지의 코드를 실행시키면 다음과 같이 온도 변화 앱을 실행할 수 있다.

profile
안녕하세요.

0개의 댓글