Skip to main content
SBM blog CTA mobile 1

옴니채널 비즈니스 메시징으로 당신의 비즈니스를 성장 시키고 비용을 줄이세요

센드버드 UIKit의 메시지 템플릿 개발하기

Blog cover Building UI Kits message template
May 15, 2023
Doo rim
Doo Rim
Software Engineer, Client Android
SBM blog CTA mobile 1

옴니채널 비즈니스 메시징으로 당신의 비즈니스를 성장 시키고 비용을 줄이세요

SBM blog CTA mobile 1

옴니채널 비즈니스 메시징으로 당신의 비즈니스를 성장 시키고 비용을 줄이세요

현재 UIKit을 사용하는 고객들은 아래 5가지의 메시지 타입만을 사용할 수 있습니다.

  • Text, Image, Video, Audio, File

하지만, 현대의 메시징 앱은 5가지 형태 이외에도, 맵, 보이스, 혹은 버튼을 포함한 메시지 등 다양한 형태의 메시지 UI를 제공합니다. 센드버드 UIKit은 고객이 원하는 형태의 메시지를 제공하기 위해, 메시지 템플릿 피처를 제공하기로 했습니다.

메시지 템플릿 개발 원칙

UIKit의 메시지 템플릿 v1은 세가지 철학을 도모하여 구현되었습니다:

  • Easy : 고객이 직관적으로 이해할 수 있어야 한다.

  • Flex : 다양하게 확장이 가능하고, 고객이 원하는 UI를 모든 플랫폼에서 그릴 수 있어야 한다.

  • Fast : 1개월 남짓한 시간 내에 만들 수 있는 스펙이어야 한다.

Markdown syntax vs Custom syntax

Markdown syntax

Easy, Fast 철학에 비추어봤을 때, 우리는 Markdown syntax의 사용을 고려했습니다. Markdown syntax는 여러 장점을 가지고 있습니다:

  • 이미 Syntax가 정해져있기 때문에 Syntax를 정의하는 과정을 스킵할 수 있다.

  • Markdown은 이미 사용자들이 학습되어 있다.

  • 오픈 소스를 활용하여 빠르게 적용이 가능하다.

위와 같은 장점에도 불구하고 Markdown syntax를 구현하는데에는 아래와 같은 단점도 존재했습니다:

  • 오픈 소스를 사용할 경우 프로덕트 퀄리티를 보장하기 힘들다.

  • 오픈 소스의 오류가 발생할 경우 수정이 어렵다.

  • 오픈 소스를 사용하지 않는다면 구현에 큰 비용이 발생한다.

  • 플랫폼 간의 디테일한 UI 차이가 발생할 수 있다.

  • 다양한 형태의 메시지로 확장하기가 어렵다.

  • 사용자 액션 정의 시 링크만 존재하기 때문에 확장이 어렵다.

Custom syntax

Custom syntax는 Markdown syntax를 구현하는것보다 난이도는 상대적으로 높고, 구현 시간이 많이 소모됩니다. 하지만, Flexibility 측면에서 많은 장점을 갖고 있고, 이후 유지 보수 측면에서도 Markdown 보다는 수월할 것이라 판단했습니다:

  • 오픈 소스를 사용하지 않으므로, 퀄리티가 보장된다.

  • 사용자 액션을 다양하게 정의할 수 있으므로, Link 이외의 액션도 받을 수 있다.

  • 다양한 형태의 메시지로 확장이 용이하다.

그러나, 한정된 리소스와 시간으로 인해 Custom syntax는 아래와 같은 단점도 있었습니다:

  • 각 플랫폼 간의 Syntax에 따른 상세 정의 비용이 높다.

  • 모든 요구사항을 만족하는 완벽한 Syntax 구현에는 개발 시간이 많이 필요하다.

  • Syntax에 대한 이해와 학습이 필요하다.

위와 같은 장단점을 비교 논의 한 결과, 최종적으로 센드버드의 UIKit은 Custom syntax로 정의하고 구현하기로 결정했습니다. 추후 확장성과 코드 퀄리티 측면에서 Markdown syntax는 치명적인 한계가 존재했기 때문입니다. Custom syntax를 채택하는 대신, 1개월 내에 구현 가능한 Syntax를 논의하여 설계했으며, 이를 v1 버전으로 진행하기로 결정했습니다.

Flexible form vs Template form

다음으로, 우리가 정의한 Syntax가 어떤 형태로 사용될지 또는 사용하도록 유도할지 정의할 필요가 있었습니다.

Flexible form

Flexible form은 사용자가 Custom syntax에 맞춰 JSON을 직접 구성하여 전달하는 형태입니다. Flexible form는 아래와 같은 장점을 가집니다:

  • 자유롭게 어떠한 UI를 만들 수 있다.

  • Template form으로 변환하여 사용 가능하다.

Flexible form은 Flex 철학에 부합하지만, 단점도 존재합니다:

  • 사용자의 러닝커브가 높다.

  • 다양한 뷰와 인터페이스를 제공하기까지 개발 비용이 크다.

  • View를 구성하는데 필요한 데이터(JSON)가 크고, Value와 분리되지 않아 Network의 Payload 및 Server-side DB 스토리지 비용이 높다.

U Ikit Mobile content offer background

The only UIKit you need.

Template form

Template form은 미리 만들어진 Layout 리스트를 정의하고, 그 리스트 내에 Value들만 분리된 형태로 제공하는 방식을 의미합니다. Template form은 Flexible UI를 커버하기에는 한계점이 있지만 다양한 장점이 있습니다:

  • 사용자들의 학습 부담이 줄어든다.

  • Template을 통해 View와 Data를 분리 시킬 수 있기 때문에 Network Payload 및 Server-side Storage DB 비용을 줄일 수 있다.

  • UI Rendering 시, Template 별로 캐쉬가 가능하기 때문에 재사용에 용이하다.

Template form은 Easy한 철학에 적합합니다. 그러나 아래와 같은 단점이 존재합니다:

  • 정해진 몇가지의 widget들로 구성을 해야하기 때문에 확장이 어렵다.

  • 요구사항에 대한 반영을 할때마다 업데이트가 필요할 수 있다.

결론적으로는, 우선 두 가지의 방식 중 Template form에 가까운 방식으로 Message template 피처를 제공하기로 했습니다. UIKit 내부적으로는 Flexible form을 받을 수 있도록 개발하되, 사용하는 측면에서 Template form으로 사용이 가능하도록 기획했습니다. 내부적으로 Flexible form이 구성되어 향후에는 고객이 Template을 직접 만들 수 있도록 하는 Design Tool을 제공할 수도 있습니다. 추가적으로, Payload를 줄이기 위해서 UI Template과 Data를 분리를 하는 것은 아직 숙제로 남아있습니다.

위의 논의 내용을 고려하여 Syntax v1을 구성하였습니다. UIKit에서 Message template을 그려주는 Custom syntax v1에 대해 소개하겠습니다.

Syntax v1

Blog image syntax v1

View라는 메시지를 그리는데 필요한 기본적인 UI 컴포넌트를 기반으로 5가지 컴포넌트를 제공합니다:

  • Box : View의 Layout을 확장할 수 있는 UI 컴포넌트

  • Text : Text를 그리는 UI 컴포넌트

  • TextButton : Text가 들어간 Button을 그리는 UI 컴포넌트

  • Image : Image를 그리는 UI 컴포넌트

  • ImageButton : Image가 들어간 Button을 그리는 UI 컴포넌트

Syntax v1에서는 Text, TextButton, Image, ImageButton의 4가지 UI 컴포넌트들을 Box 안에서 다양한 형태로 구성할 수 있습니다. 각각의 UI 컴포넌트의 다양한 속성을 정의할 수 있는 Style class들도 존재합니다.

각 Platform은 Syntax v1을 가지고 다양한 형태의 메시지를 그려주기 위해 Parser, Renderer를 구현하였습니다. 아래의 그림과 같이 Parser를 통해서 View type, Style data 등을 Parsing합니다. 각 플랫폼은 파싱된 데이터를 가지고 Renderer를 통해 메시지를 그려줍니다. Renderer는 파싱된 데이터와 일치하는 플랫폼의 기본 View 컴포넌트로 메시지를 그려줍니다.

Blog parser and renderer

Parser와 Renderer를 구현하는 과정에서 플랫폼 별 차이로 인해, UI 통일성을 위해 고민할 사항이 존재했습니다. 해당 내용에 대해서 살펴보겠습니다.

플랫폼의 차이

UIKit은 Android, iOS, React, React native 총 4개의 Platform을 지원합니다. 4개의 서로 다른 플랫폼에서 하나의 통일된 Syntax를 정의하고 구현하기 위해서는, Platform 간의 차이로 인해 해결해야 하는 문제점들이 있었습니다.

기본 제공 컴포넌트

모든 플랫폼은 UI를 그리는 기본 View 컴포넌트들을 제공합니다. 기본 View 컴포넌트가 UI에 필요한 속성값들을 제공합니다. UIKit에서는 Syntax를 기반으로 기본 View 컴포넌트에 적용합니다. 하지만, 이 과정에서 플랫폼 간의 차이가 있었습니다. 예를 들면, Button을 구성할 때, 각각의 플랫폼은 아래와 같은 버튼 컴포넌트를 사용하여 Button을 그립니다.

Android

open class Button : TextView
open class ImageButton : ImageView

iOS

class UIButton : UIControl

React

const Button = ({ value }) => (<button>{value}</button>);

React native

const Button = () => (<Pressable><Text /></Pressable>);
const ImageButton = () => (<Pressable><Image /></Pressable>);

각 플랫폼에서 제공하는 적절한 View 컴포넌트를 선택하고 조합하여 Text button, Image button을 그려줍니다.

용어 차이

각 플랫폼 별로 적용하는 개념은 유사하지만 표현하는 용어가 다른 경우가 있습니다. 예를 들면, Layout을 배치하는 모델을 만들기 위해 고민했습니다. 각각의 플랫폼에서 Layout 구성을 담당하는 클래스는 아래와 같았습니다.

Android

class ConstraintLayout : ViewGroup
open class LinearLayout : ViewGroup
open class GridLayout : ViewGroup

iOS

class NSLayoutConstraint : NSObject
class NSLayoutAnchor<AnchorType: AnyObject>: NSObject
class NSLayoutDimension: NSLayoutAnchor<NSLayoutDimension>

React 와 React-Native 에서는 Layout 구성을 위해 잘 알려진 Flexbox를 사용합니다.

React

const Box = ({ children, direction }) => (
  <div style={{ display: 'flex', flexDirection: direction }}>
    {children}
  </div>
);

React native

const Box = ({ children, direction }) => (
  <View style={{ flexDirection: direction }}>
    {children}
  </View>
);

우리는 통일되고 직관적인 이름이 필요했습니다. 최종적으로 Box를 선택했습니다. Box는 방향성(row, column)을 가진 ViewGroup입니다. 각각의 플랫폼은 Syntax를 통해 받은 Box 안의 속성을 가지고 Layout을 배치합니다.

디스플레이 단위

View를 구성하는 요소 중 단위를 표현하는데에는 px, dp, sp, pt, rem, em 등이 있습니다. 각각의 플랫폼에서 사용하는 단위가 다르기 때문에 이를 하나로 맞추는 작업이 필요했습니다. 아래는 각각의 플랫폼에서 사용하는 단위에 대한 정보입니다.

Android

  • UI 표시 크기를 유지하기위해 밀도 독립형 픽셀(dp) 사용

  • dp (Density-independent pixel) : 디스플레이 해상도와 독립적으로 UI의 크기를 표현하는 데 사용되는 측정 단위

iOS

  • UI 표시시 point(pt) 단위 사용

  • pt는 iOS 기기의 해상도에 따라 px를 계산

  • 애플 공식 문서

React

  • React에서는 고정된 디스플레이 단위를 위해서 px를 사용했습니다

React native

  • React Native에서 모든 크기는 단위가 없지만, 밀도에 독립적인 픽셀(dp) 을 나타냅니다.

Syntax에서는 Size를 표기하는 방식으로 Integer 값을 받도록 정의했습니다. Integer 값은 Pixel이 아닙니다. 각각의 플랫폼에서 사용하는 디스플레이 단위로 인식합니다. Android는 dp, iOS는 pt, React는 px, React native는 Integer로 인식합니다. 이를 통해 하나의 Integer 값으로 디스플레이에 따라 통일성 있는 UI의 구현이 가능합니다.

UI 구현

Layout

2차원 평면 디스플레이 안에 다양한 배치를 제공하기 위해 Box 컴포넌트가 Layout을 담당합니다. Box는 Linear, Grid 또는 Grid 내부의 Grid를 그리는 복잡한 UI 모두를 커버합니다. 이를 위해, Box는 layout 이란 속성 안에 row, col 값을 제공합니다.

Blog mobile layouts

기본적인 Linear 형태는 수평, 수직 형태에 따라 1개의 Box로 구현이 가능합니다. 1개의 박스 안에 Text, TextButton, Image, ImageButton 등을 배치하면 Linear하게 메시지 UI를 그릴 수 있습니다.

Grid한 형태를 그리기 위해서는 Box 안의 Box가 들어가야 합니다. Box안에는 제한 없이 Box가 들어갈 수 있습니다. Box의 depth를 조절하여 모든 형태의 2차원 배치를 완성할 수 있습니다.

하지만, 모든 2차원 평면을 커버하더라도 한 공간을 차지하는 View의 사이즈에 따라 공간 안에서의 배치를 다르게 하고 싶을 수 있습니다. 이를 위해 align 속성을 제공합니다. Align 속성을 통하여 배치된 공간 안에서 9가지 방향을 결정할 수 있습니다.

Blog center block

Size

객체의 사이즈를 모두 정확히 지정할 수 있으면 좋겠지만, 그렇지 않은 경우가 존재합니다. 일반적인 메시지는 텍스트 길이 따라 텍스트를 감싸는 영역의 크기가 달라집니다.

Blog building UIKit text message

내부 컨텐츠의 값에 따라 사이즈가 변경되는 경우를 커버하기 위해 size를 fixed, flex type으로 구분합니다. Flex 타입에는 위와 같이 content 영역 만큼 size를 잡는 wrap_content 값과 부모 영역만큼 size를 차지하는 fill_parent 두 가지 값을 제공합니다.

Padding

Message template 피처에서는 Default padding이 존재합니다. UI를 그리면서 Default padding 값이 0으로 되어있다면, 각 View마다 적절한 Padding 값을 적용해야 합니다. 이 때, 고객의 사용성에 대해 큰 불편함이 있습니다:

  • 기존의 UIKit과 통일된 UI를 보여주기 위해 Padding 값을 추론해야 한다.

  • 모든 View에 Padding이 적용되면 Payload가 커진다.

  • 모든 Platform마다 UI를 확인해야 하므로 디버깅이 어렵다.

Default padding이 없다면, 대부분의 View에 아래와 같이 Padding 속성이 추가될 것 입니다.

{
    "type": "text",
    "viewStyle": {
        "padding": {
            "top": 4,
            "bottom": 4,
            "left": 4,
            "right": 4
        }
    },
    "text": "Lorem ipsum dolor sit ameti",
    "textStyle": {
        "size": 14
    }
}

UIKit 내부적으로 default padding을 적용하기 때문에 쉽게 JSON syntax를 구성할 수 있습니다.

{
    "type": "text",
    "text": "Lorem ipsum dolor sit ameti",
    "textStyle": {
        "size": 14
    }
}

이러한 불편함을 해소하고자, 기존 UIKit의 UI와 통일성을 주는 Padding을 각각의 플랫폼에서 적절히 적용합니다. iOS의 경우, 모든 View가 Padding 속성을 가지고 있지 않기 때문에 Padding을 위한 커스텀뷰가 설계되었습니다.

Action 구현

메시지 UI를 클릭했을 때, 클릭에 대한 액션을 제공합니다. 액션에 대한 모든 경우를 커버하기 위해 3가지 type을 제공합니다.

  • web : 웹링크를 값으로 받아 해당 링크로 연결합니다.

  • uikit : 사전에 정의된 uikit 내부 동작을 실행합니다.

  • custom : 고객에 app에서 직접 정의한 동작을 실행합니다.

uikit 타입의 대표적인 예시로 message의 delete 액션을 들 수 있습니다. delete에 대한 액션은 UIKit 내부에서 메시지를 지워주는 동작입니다. UIKit 내부적으로 수행이 가능한 동작들을 uikit type을 통해 쉽게 구현할 수 있습니다.

custom 타입은 고객의 앱 내부 동작을 수행하거나, uikit 타입이 지원하지 않는 UIKit의 동작 액션에 대해서 수행할 수 있습니다. 또한, 외부 앱으로 이동을 하는 동작도 custom type을 통해 구현할 수 있습니다. custom 타입의 경우에는 메시지 빌더만으로는 동작이 어려운 단점이 있습니다. 고객의 앱에서 직접 동작을 구현해야 하기 때문입니다.

Action Data는 기본적으로 UIKit 내부 로직에 의해 처리합니다. 하지만, Action Data가 항상 모든 플랫폼에서 처리 가능한 형태라는 보장은 없습니다. 그렇기 때문에, 예외적인 상황을 처리하기 위해, Alter Data를 추가적으로 지원하기로 결정했습니다. Action Data를 사용하여 UIKit 내부 처리가 실패했을 때, Alter Data를 사용하여 예외 처리가 가능하도록 지원합니다.

Sendbird Notifications

Message template을 활용한 첫번째 프로덕트로 Sendbird Notifications를 소개합니다.

Blog Sendbird message builder

Sendbird message builder (SMB)를 통해서 Notifications로 메시지를 보낼 수 있습니다. 이 때, SMB를 사용하여 정의된 템플릿으로 다양한 메시지를 구축할 수 있습니다. 구축된 메시지는 UIKit의 Notifications로 전송됩니다. 더 효과적인 마케팅과 알림 목적으로 Sendbird Notifications 프로덕트가 활용될 수 있을 것이라 기대합니다.

Ebook Grow Mobile content offer background

비즈니스 성과로 이어지는 디지털 커뮤니케이션

센드버드와 함께, 지금 바로 시작해보세요