Devfest Korea week 1-1 Compose Basic

Devfest Korea를 참여하면서 정리하게 된 글이다.
컴포즈가 필요한 이유

Week 1 Compose

Compose가 태어나기전 지난 역사들

보통 앱을 만든다하면 XML에서 UI를 그리고 이어진 Class에서 프로그래밍을 했다.
그리고 XML에의 UI를 접근하려면 find를 통해 찾아야하는 무식한 방법을 썼다.


지금 생각해보면 어떻게 코딩했나 싶다..

이것의 문제점은 UI에서 컨트롤할 객체들이 많아지면 많아질수록 선언해야할게 많아지는것이다.

이러한것을 타개하기위해 구글은 DataBinding 해결책을 냈다.


좀더 코딩이 쉬워졌다. 더이상 ID를 찾을수없다는 에러가 종식되었다.

이것까지가 현재의 시점이다.

쓰다보니 기존적인 문제점은 탈피하지 못했다.

  • UI와 작동 코드가 분리되어있다는 문제
  • UI를 컨트롤하는 세팅과정중 불필요한 보일러 플레이트 코드 생성
  • Fragment의 까다로운 생명주기

컴포즈 시작하기

더이상 SetContentbinding.root를 안붙여도 된다.

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           BasicsCodelabTheme {
               // A surface container using the 'background' color from the theme
               Surface(color = MaterialTheme.colors.background) {
                   Greeting("Android")
               }
           }
       }
   }
  @Composable
private fun Greeting(name: String) {
   Surface(color = MaterialTheme.colors.primary) {
       Text (text = "Hello $name!")
   }
}

Surface는 컴포넌트들을 Wrap한다고 볼수있다.
테마를 입힌다거나 클릭이벤트를 이곳에서 줄수도있다.

열과 행 만들기

Compose의 표준 레이아웃은 Column, RowBox이다.

@Composable
private fun Greeting(name: String) {

   Surface(
       color = MaterialTheme.colors.primary,
       modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
   ) {
       Row(modifier = Modifier.padding(24.dp)) {
           Column(modifier = Modifier.weight(1f)) {
               Text(text = "Hello, ")
               Text(text = name)
           }
           OutlinedButton(
               onClick = { /* TODO */ }
           ) {
               Text("Show more")
           }
       }
   }
}

Surface를 통해 수직 4dp , 수평 8dp를 적용했다.

텍스트와 버튼을 양쪽에 적용하기 위해 Row 를 넣고,

weight를 통해 반반을 적용했다.

상태 적용하기

show less를 클릭할경우, 확장이 되어야 한다.

@Composable
private fun Greeting(name: String) {
   var expanded = false // Don't do this!

   Surface(
       color = MaterialTheme.colors.primary,
       modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
   ) {
       Row(modifier = Modifier.padding(24.dp)) {
           Column(modifier = Modifier.weight(1f)) {
               Text(text = "Hello, ")
               Text(text = name)
           }
           OutlinedButton(
               onClick = { expanded = !expanded }
           ) {
               Text(if (expanded) "Show less" else "Show more")
           }
       }
   }
}

이렇게 코드를 짤경우, 아무런 반응이 없다.

Compose는 상태를 observe하고, 변경이 될경우 UI를 업데이트하는데,
단순한 Boolean값은 업데이트하지 않는다.

해결책은 mutableStateOf를 적용하는 것이다.

@Composable
private fun Greeting(name: String) {

   val expanded = remember { mutableStateOf(false) }

   val extraPadding = if (expanded.value) 48.dp else 0.dp

   Surface(
       color = MaterialTheme.colors.primary,
       modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
   ) {
       Row(modifier = Modifier.padding(24.dp)) {
           Column(modifier = Modifier
               .weight(1f)
               .padding(bottom = extraPadding)
           ) {
               Text(text = "Hello, ")
               Text(text = name)
           }
           OutlinedButton(
               onClick = { expanded.value = !expanded.value }
           ) {
               Text(if (expanded.value) "Show less" else "Show more")
           }
       }
   }
}

단순히 mutableStateOf를 적용할 경우, UI가 업데이트 될때마다
계속 false를 호출시킬것이다.
remember를 호출하면 업데이트 되어도 값을 유지할 수 있다.

레이아웃 숨기기

Layout을 숨길때 , 우리는 visibleinvisible을 이용한다.
Compose에서는 이러한 기능을 제공하지 않고, 코드로 표현을 해줘야한다.

fun MyApp(){
var shouldShowOnboarding by remember { mutableStateOf(true) }
   if (shouldShowOnboarding) {
       OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
   } else {
       Greetings()
   }

}

// OnboardingScreen

@Composable
fun OnboardingScreen(onContinueClicked: () -> Unit) {
   Surface {
       Column(
           modifier = Modifier.fillMaxSize(),
           verticalArrangement = Arrangement.Center,
           horizontalAlignment = Alignment.CenterHorizontally
       ) {
           Text("Welcome to the Basics Codelab!")
           Button(
               modifier = Modifier.padding(vertical = 24.dp),
               onClick = onContinueClicked
           ) {
               Text("Continue")
           }
       }
   }
}

숨겨야하는 레이아웃을 함수로 만들었을때, 클릭이벤트를 설정해야하는데,
MyApp()에서는 클릭이벤트를 그냥 전달받을수 없기 때문에,
Listner를 만들어줘야한다.

onContinueClicked: () -> Unit 이것을 이용해
Listner 파라미터를 만들어서

ButtonOnClick을 이용하여 Listner를 받는다.
클릭할 경우, 아래의 Listner가 실행되는것이다.
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })

RecycleView 이용하기

위의 Greetings를 100번실행시키면 어떻게 될까?
ListView가 사라진 이유가, OutOfMemory에 대한 이슈 때문이다.
1000개를 보여줘야하는데 대략 500개가 넘어가버리면 핸드폰의 메모리가 터져버리는것이다.

이와 같이, Compose에서는 Lazy를 이용하여 RecycleView를 만들수 있다.

@Composable
private fun Greetings(names: List<String> = List(1000) { "$it" } ) {
    LazyColumn(modifier = Modifier.padding(vertical = 4.dp)) {
        items(items = names) { name ->
            Greeting(name = name)
        }
    }
}

item이라는 함수를 이용하여, 가져오기를 수행할수있다.
현재 name이라는 명칭을 만들어, Greeting에 파라미터를 던져주는것을 볼수있다. (DTO클래스일경우가 일반적이다.)

앱 종료시, 상태 저장하기

우리는 앱에 만들때마다 lifecycle에 생각해야한다.
핸드폰에서 회전하기, 다크모드 등 앱이 종료되서 다시 onCreate
로 실행되는 경우가 많다.

이 경우, 데이터가 초기화되고 레이아웃또한 원상태로 돌아가는데,

이상태를 막기위해, MVVM 패턴과 saveState를 적용하였다.
ViewModel의 저장된 상태 모듈
savedstatehandle을 이용하는법

아직 ViewModel을 들어가지 않아서 모르겠지만,
기본적으로 간단하게 rememberSaveable을 이용하여 값을 저장할수있다.

var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }

애니메이션 만들기

밋밋하다. 요즘 트랜가 간편이 최소화라지만, 애니메이션을 넣어주고싶다.
회사 업무에서 애니메이션을 넣은적이 없지만, Compose에서나마 꿈을 이룰수 있었다.

@Composable
private fun Greeting(name: String) {

    var expanded by remember { mutableStateOf(false) }

    val extraPadding by animateDpAsState(
        if (expanded) 48.dp else 0.dp,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioMediumBouncy,
            stiffness = Spring.StiffnessLow
        )
    )

    Surface(
    ...
            Column(modifier = Modifier
                .weight(1f)
                .padding(bottom = extraPadding.coerceAtLeast(0.dp))

    ...

    )
}

animateDpAsState()는 애니메이션이 끝날때까지 값이 지속적으로 업데이트 되는 객체이다.
animateDpAsStateanimationSpecoptional하게 받는데 상세하게 애니메이션을 정의해줄수 있다.

테마 수정하기

private val DarkColorPalette = darkColors(
    surface = Blue,
    onSurface = Navy,
    primary = Navy,
    onPrimary = Chartreuse
)

private val LightColorPalette = lightColors(
    surface = Blue,
    onSurface = Color.White,
    primary = LightBlue,
    onPrimary = Navy
)

@Composable
fun BasicsCodelabTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
        colors = colors,
        typography = typography,
        shapes = shapes,
        content = content
    )
}

theme.kt에서 다크모드가 될 경우나 혹은 기본적인 navy색깔을 변경할 경우, 위의 DarkColorPaletteLightColorPalette의 수정으로 변경이 가능하다.

또한 어떤 컴포넌트에서 수정이 가해질때,
Text(text = name, style = MaterialTheme.typography.h4)
style을 사용한다.

그리고 기존 색상이나 스타일에서 살짝 바꿔야할 경우 copy를 사용한다.

// 현재 h4의 유형에서 굵기를 굵게하기위해 copy를 썼다.
       Text(
            text = name,
            style = MaterialTheme.typography.h4.copy(
            fontWeight = FontWeight.ExtraBold
             )
      )

좋은 웹페이지 즐겨찾기