Compose Multiplatform 첫걸음 떼기

2023년 10월 25일 | 기술 이야기

안녕하세요. 와디즈 앱개발팀 Android 앱 개발자입니다.

지난 7월 와디즈는 혜택 페이지를 개발했어요. 그때 ‘웰컴 쿠폰’ 발급 화면에 Server Driven UI를 도입했습니다. Server Driven UI란 서버에서 UI 디자인 정보를 제공하면, 클라이언트 앱에서 동적으로 화면을 그릴 수 있게 해주는 메커니즘이에요. 앱을 새로 배포하지 않고도 UI 변경이 가능해요.

Compose Multiplatform

Server Driven UI에서 사용하는 JSON 예시

덕분에 ‘웰컴 쿠폰’ 페이지를 더 편리하고 원활하게 운영할 수 있게 되었어요! 하지만 매번 JSON을 작성하고 변경하는 것은 복잡하고 어려운 일이었죠. 페이지를 운영하는 누구나 쉽게 UI를 관리할 수 있도록 관리자 도구 개발이 필요했습니다. Compose Multiplatform을 도입해 개발하게 되었는데요. 그 경험을 간단히 소개합니다.

 

Compose Multiplatform 이란

Compose Multiplatform은 JetBrains에서 제공하는 UI 프레임워크에요. Android 개발자들에게 친숙한 Kotlin과 Compose를 사용하여 데스크탑 앱과 모바일 앱을 동시에 개발할 수 있는 도구죠. 일부 실험 기능도 있지만 iOS, Android, Desktop, Web 모두를 지원합니다.

iOS 앱 개발을 시작하려면, macOS, Java, Android Stuido, Xcode, Cocoapods이 필요해요. KDoctor를 설치하고 실행해 아래와 같이 준비되었는지 확인합니다. 추가로, Kotlin Multiplatform Mobile 플러그인 설치도 권장해요.

Compose Multiplatform

 

Compose Multiplatform 시작하기

Compose Multiplatform

intelliJ IDEA

우선 시작하려면 intelliJ IDEA가 필요해요. New Project → Compose Multiplatform를 선택한 후 프로젝트명, 저장 경로, 패키지명 등을 입력합니다.

Configuration

  • Single platform : desktop 또는 web 중 단일 플랫폼 지원으로 충분할 때 선택해요.
  • Multiple platform : desktop, mobile, web(Experimental) 등 다중 플랫폼 지원이 필요할 때 선택해요.

프로젝트의 패키지를 살펴볼게요.

Compose Multiplatform

프로젝트 내에는 common 모듈과 각 플랫폼 모듈이 구성되어 있어요. common 모듈은 플랫폼에 종속되지 않는 공통 코드를 포함하고 있는데요. Android, 데스크탑과 같이 플랫폼 의존적인 코드는 각각의 플랫폼 모듈에 작성되어 있어요.

  • common

    • commonMain : 플랫폼 의존성이 없는 코드

    • androidMain : android actual 함수, 클래스 작성

    • desktopMain : desktop actual 함수, 클래스 작성

  • android : android 플랫폼 종속 코드

    • AndroidManifest.xml

  • desktop : desktop 플랫폼 종속 코드

이 프로젝트에서는 대부분의 코드를 commonMain 패키지에 작성할 수 있었어요. Compose를 사용해, 그린 화면은 플랫폼에 종속되지 않기 때문이죠.

그러나 이미지 로딩, 파일 저장 경로 설정, 다이얼로그 표시와 같이 일부 기능들은 각 플랫폼에서 사용하는 라이브러리와 환경이 다릅니다. 플랫폼에 맞춰 코드를 분리하고 효율적으로 관리하기 위해 Compose Multiplatform에서는 expect와 actual이라는 키워드를 사용해요.

Compose Multiplatform

자바 인터페이스와 동일한 개념으로 common 모듈에서 expect 함수 또는 클래스를 명시하면, 각 플랫폼(android, desktop)에서 actual을 붙여 구현 함수 또는 클래스를 작성해요. 사용하는 라이브러리 추가도 동일하게 build.gradle에서 각 플랫폼에 맞게 작성합니다.

Compose Multiplatform

serialization-json 라이브러리는 플랫폼에 독립적이기 때문에 commonMain에 추가했어요. material3 라이브러리는 Android 및 데스크탑 플랫폼, 서로 다른 라이브러리를 사용해야 하는데요. 그래서 각 플랫폼에 별도로 추가했습니다. 그 결과, 여러 플랫폼에서 필요한 라이브러리를 효율적으로 관리할 수 있게 되었어요.

android material3

androidx.compose.material3:material3:1.0.1

desktop material3

org.jetbrains.compose.material3:material3-desktop:1.3.0

 

코드 작성해 보기

다음은, 화면에 UI 컴포넌트를 추가하는 기능을 만들었어요. 리스트를 보여주는 LazyRow Composable 함수를 생성해 각 옵션의 이름과 이미지를 보여줍니다.

Compose Multiplatform

@Composable 
private fun ComponentOptionItems(
    clickAction: (ServerComponentType) -> Unit 
) { 
    Spacer(modifier = Modifier.background(Color.Black).fillMaxWidth().height(1.dp)) 
    LazyRow(
        modifier = Modifier.fillMaxWidth() 
            .height(200.dp)
            .background(Color.White), 
        horizontalArrangement = Arrangement.spacedBy(12.dp) 
    ) { 
        val typeList = ServerComponentType.values()
        items(typeList) {
            Column( 
                modifier = Modifier.fillMaxHeight().clickable { 
                    clickAction.invoke(it)
                }, 
                horizontalAlignment = Alignment.CenterHorizontally 
            ) { 
                Text( 
                    modifier = Modifier.padding(16.dp),
                    text = it.name, 
                    color = Color.Black, 
                    fontWeight = FontWeight.Bold 
                ) 
                getPainterFile(it.name.lowercase())?.let { painter -> 
                    Image(
                        painter = painter, 
                        contentDescription = it.name 
                    ) 
                } 
            } 
        } 
    } 
}

Compose Multiplatform

var openDialog: Pair<ServerComponentType, Component?>? by remember { 
  mutableStateOf(null) 
} 

showOptionSettingDialog(openDialog?.first, openDialog?.second, deleteAction = { ... }) 

ComponentOptionItems { 
  openDialog = Pair(it, null) 
}

openDialog라는 remember 변수를 생성하고, ComponentOptionItems 아이템을 클릭했을 때 openDialog를 변경합니다. showOptionSettingDialog에서는 openDialog 상태가 변경되었기 때문에 다시 그려져요.

android와 desktop에서 다이얼로그를 보여주는 방법이 다른데요. 따라서 expect 함수를 정의하고 각 플랫폼에 맞게 actual 함수를 작성합니다.

// common 
@Composable 
expect fun showOptionSettingDialog( 
  type: ServerComponentType?, 
  baseComponent: Component?, 
  deleteAction: () -> Unit, dismissAction: (Component?) -> Unit 
  ) 

// android 
@Composable 
actual fun showOptionSettingDialog( 
  type: ServerComponentType?, 
  baseComponent: Component?, 
  deleteAction: () -> Unit, 
  dismissAction: (Component?) -> Unit 
  ) { 
    if (type != null) { 
      var component by remember { mutableStateOf(baseComponent) } 
      Dialog(
        onDismissRequest = { dismissAction.invoke(null)},
        properties = DialogProperties(usePlatformDefaultWidth = false), 
        content = { 
            ... // 옵션 화면 그리기 
        } 
      ) 
    } 
}

// desktop 
@Composable 
actual fun showOptionSettingDialog( 
  type: ServerComponentType?, 
  baseComponent: Component?, 
  deleteAction: () -> Unit, 
  dismissAction: (Component?) -> Unit 
  ) { 
    if (type != null) {
      var component by remember { mutableStateOf(baseComponent) } 
      Dialog( 
        title = type.name, 
        visible = true, 
        state = DialogState(size = DpSize(1200.dp, 800.dp)), 
        onCloseRequest = { 
            dismissAction.invoke(null) 
        }) { 
          ... // 옵션 화면 그리기 
        } 
    } 
}

언뜻 보면 android, desktop 모두 Dialog라는 Composable 함수를 호출하고 있지만, 다른 함수예요.

// android 
androidx.compose.ui:ui AndroidDialog.android 

// desktop 
org.jetbrains.compose.ui:ui-desktop Dialog.desktop

각 다이얼로그에서 그리는 옵션 화면은 플랫폼 상관없이 동일해요. 그래서 common에 정의한 Composable 함수를 호출해 공용으로 사용합니다.

Compose Multiplatform

데스크탑 앱

Compose Multiplatform

안드로이드 앱

 

이렇게 Compose Multiplatform을 통해 개발한 결과물은 데스크탑과 Android 앱에서 실행할 수 있어요. Server Driven UI 화면을 구성하여 미리보기 화면도 볼 수 있고요. JSON 데이터를 추출해 파일로 저장도 가능합니다.

이전에는 JSON을 만들고 각 플랫폼에서 테스트해야 하는 번거로움이 있었는데요. 그런 과정 없이 관리자 도구로 UI를 확인할 수 있어서 매우 편리했어요.

Compose Multiplatform

 

 

마치며

데스크탑 앱 개발은 처음에는 어려워 보였지만 새로운 도전이라는 점에서 설레기도 했어요. Compose도 활용해 볼 수 있어, 개발 후 두 마리 토끼를 잡은 듯 기분이 좋았죠.

다만 몇 가지 아쉬움도 남아 있어요. 피드백에서, 다이얼로그보다는 한 화면에서 수정할 수 있는 Drawer Navigation 방식이 더 편리할 것 같다는 의견이 있었는데요. 이를 참고해 UI/UX를 개선해 볼 생각이에요.
또 운영 담당자가 편리하게 사용할 수 있도록, lineHeight, gravity, margin 등의 개발 용어로 작성된 부분을 누구나 이해하기 쉬운 용어로 바꿀 예정이고요.

앞으로 Server Driven UI와 Compose Multiplatform으로 가능한 것이 무궁무진할 것으로 기대돼요. 실험 기능에서 업데이트될 기능들도 궁금하고요. 의미 있는 경험을 얻을 수 있어서 즐거웠습니다. 🙂🚀

 

 

궁금한 내용이 남아 있나요? 👀

앱개발팀의 조직 문화가 궁금하다면? 👉클릭
앱개발팀은 인스타그램 공유하기 기능도 만들었어요! 👉클릭
채용 중인 개발 직군 보러 가기 👉 클릭

👇 태그를 클릭하면 같은 키워드의 글을 모아볼 수 있어요.

기술 이야기

기술 이야기

기술 조직

누구나 도전하고, 새로움을 경험할 수 있도록, 꼭 필요한 서비스를 만듭니다.