ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Compose 개요
    개발/Android 2024. 3. 9. 20:33

     Android 개발을 하면서 힘든점을 꼽으라면 자주 바뀌는 개발 트렌드라고 생각한다. 물론 다른 분야(백엔드, 프론트 등등)을 해본건 아니여서 상대적인 비교를 할 수는 없지만 3년동안 일을 하면서 매번 새로운 기술을 익히며 적용했다. MVVM, CleanArchitecture, ViewModel, DataBinding, Flow 등등 당장 생각나는 것만 이정도가 있다. 확실히 처음 공부했을 때 보던 일반적인 구조의 앱과 실무는 많은 차이가 있다고 느꼈다.

     

     이제 위에 나열한 기술을 적용해보고 어느정도 익숙해져서 이제는 어느정도 최신 트렌드를 다뤄봤다! 라고 생각했지만 Compose가 남았다. Compose가 나온지는 좀 됐고 뭔지 대충은 알고 있었지만 프로젝트 경험이 없었고 그렇게 대세가 될까 하는 생각도 들었다. 왜냐하면 기존 View를 다루는 방식과의 차이가 너무 컸기 때문이다. Compose에서는 기존 Activity or Fragment 의 LifeCycle 을 기반으로 한 방식이 아닌 각각의 UI컴포넌트의 상태를 기반으로 View가 갱신된다. 아직 Compose를 잘 써보지 못해서 이 방식으로도 문제 없이 앱을 만들 수 있을까 생각이 들지만 이제 차근 점차 알아보려고 한다. Compose와 같은 선언형 UI가 요즘 대세이고 이미 Flutter, React, Swift 등 많은 프레임워크에서 사용하고 있다. 실제 Android Native 개발자 채용 공고에서도 우대사항에 Compose를 자주 볼 수 있다.

     

     기존의 UI 방식과 차이점이 많아 러닝커브가 어느정도 있겠지만 확실히 장점이 많은 기술처럼 보인다. 오늘부터 퇴근 후, 주말에 틈틈이 Compose 공부를 해보려한다.

     

    선언형 UI

     선언형 UI란 무엇을 어떻게 화면에 표시할지 선언하면 프레임워크가 그 방식대로 알아서 화면을 처리하는 방식의 인터페이스 구축 방법이다. 이름 그대로 UI요소를 미리 선언해 놓고 데이터를 세팅하는 작업은 프레임워크에 맡기다는 것이다. 

     

    특징

    - 상태 중심의 디자인: UI는 상태에 따라 정의되며, 상태가 변할 때 UI가 자동으로 업데이트 된다.

    - 직관적: UI의 구성 방법을 코드에 그대로 넣기 때문에 코드가 직관적이다.

    - 재사용성: UI의 개별적인 부분만 컴포넌트로 만들어 재사용할 수 있다.

     

    XML 기반 방식과의 차이

     기존방식은 xml 파일에 View를 그리고 그 id값을 참조해 데이터를 직접 세팅해줬다. 이게 말로는 간단해 보이지만 실제 코드를 보면 차이가 많이 난다.

    @Composable
    fun Greeting(name: String) {
        Text(text = "Hello, $name!")
    }

    Compose를 사용해 텍스트를 보여주는 코드이다. 아주 간단한 기능이여서 코드가 짧다고 생각되긴 하지만 xml 방식은 이 간단한 코드도 귀찮은 과정들이 필요하다.

     

    아래는 xml 기반 방식의 코드이다.

    <TextView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/greetingTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello, World!" />

    일단 layout 파일을 따로 만들어 태그로 감싸고 id 지정을 한다. 그 후 kotlin 파일에서 추가 작업을 한다.

    public class GreetingActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.greeting);
    
            TextView greetingTextView = findViewById(R.id.greetingTextView);
            greeting(greetingTextView, "Android");
        }
    
        private void greeting(TextView textView, String name) {
            textView.setText("Hello, " + name + "!");
        }
    }

    xml파일을 메모리에 로드하고 객체화 시키는 inflate 과정이 내부적으로 일어나고 id값을 찾아 직접 데이터를 세팅해준다.

     

    코드의 길이의 차이뿐 아니라 직관성도 많이 떨어진다. 물론 위의 방법은 좀 예전 방식이고 최근에는 DataBinding을 이용해 데이터를 구독하고 자동으로 업데이트 되게하는 방식도 많이 사용한다. 

     

    class MyViewModel : ViewModel() {
        private val _greetingText = MutableStateFlow("Hello, World!")
        val greetingText: StateFlow<String> = _greetingText.asStateFlow()
    }

    ViewModel을 만들어 String을 StateFlow로 받는다. 이 데이터는 네트워크나 내부 DB등에서 데이터의 흐름을 flow로 받을 것이다.

     

    class MainActivity : AppCompatActivity() {
        private lateinit var binding: ActivityMainBinding
        private val viewModel: MyViewModel by viewModels()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
            binding.lifecycleOwner = this  //StateFlow의 UI 업데이트를 위해 필요
            binding.viewModel = viewModel
        }
    }

    Activity에서는 viewModel을 선언하고 layout에서 선언된 viewmodel과 연결해준다. UI업데이트를 위해 lifecycleOwner도 세팅한다.

     

    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    
        <data>
            <variable
                name="viewModel"
                type="com.example.MyViewModel" />
        </data>
    
        <LinearLayout
            ...
            tools:context=".MainActivity">
    
            <TextView
    		    android:id="@+id/greetingTextView"
    		    android:layout_width="wrap_content"
    		    android:layout_height="wrap_content"
    		    android:text="@{viewModel.greetingText}" />
    
        </LinearLayout>
    </layout>

    xml에서는 viewModel 변수를 선언하고 TextView에서는 viewModel의 구독한 데이터를 자동으로 업데이트 해준다.

    DataBinding 방식이 동작상으로 선언형 UI와 유사해 보인다. 위에서 선언형 UI의 장점을 많이 나열했고 DataBinding이 선언형 UI와 비슷하다고 생각하지만 나는 DataBinding 방식을 선호하지는 않았다.

     

    DataBinding의 단점

     DataBinding이 이론적으로 좋아보이지만 사용하면서 느낀 불편한점들이 좀 있었다. 지극히 주관적인 내 의견이다.

     

    • 디버깅이 상대적으로 힘들다.
      xml은 로그를 남길 수도 없고 브레이크 포인트를 찍을 수도 없어서 코드로 할때보다는 디버깅이 어렵다고 느꼈다.
    • 모든 UI 업데이트를 xml 파일에서 해결할 수 없다.
      위의 예처럼 텍스트 정도야 간단하게 바꿀 수 있지만 조금 복잡해지면 BindingAdapter class를 따로 만들어 추가 작업이 필요하다.
      대표적인 예가 RecyclerView이다. 만약 구독하는 데이터가 List고 그 데이터로 RecyclerView를 구성한다면 BindingAdapter로 추가 작업이 필요하다.
    • xml 파일과 kotlin 파일 동시 사용
      DataBinding은 로직을 xml에도 적용시킨다. 로직이라기 보다는 메소드 명을 적지만 코드를 볼 때 xml과 kotlin 파일을 동시에 봐야하고 xml 파일에서도 class파일의 변수를 선언하는 작업도 추가돼야 한다.
    @JvmStatic
        @BindingAdapter("midWeatherItem")
        fun RecyclerView.bindMidWeatherItem(uiState: UiState<List<MidWeatherEntity>>) {
            uiState.successOrNull()?.let {
                val adapter = MidWeatherAdapter()
                this.adapter = adapter
    
                adapter.submitList(it)
                if (this.itemDecorationCount == 0) this.addItemDecoration(MidWeatherAdapter.MidWeatherItemDecoration())
            }
        }
    <androidx.recyclerview.widget.RecyclerView
                                android:id="@+id/mid_weather_recyclerview"
                                midWeatherItem="@{viewmodel.midWeatherUiState}"
                                android:layout_width="match_parent"
                                android:layout_height="match_parent"
                                android:orientation="vertical"
                                android:padding="15dp"
                                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

    위의 코드는 내가 공부하면서 만든 날씨 앱이다. midWeatherItem을 바인딩 해주는 메소드를 따로 만들고 그 안에서 UI 로직을 처리한다.

    이렇게 클래스 파일을 따로 또 만들어줘야 되면 그냥 DataBinding을 쓰지 않고 클래스에서 구독한 데이터로 UI를 처리하는 클래스나 메소드를 따로 만드는게 낫지 않나라는 생각이 들었다. 

     

    결론

    글을 쓰다보니 Compose보다는 기존 Android UI 처리의 불편한 점만 많이 나열한거 같다... 

    Compose는 선언형 UI 방식으로 동작하고 DataBinding과는 다르게 xml 파일 자체를 사용하지 않아도 된다. 이것만 하더라도 충분히 배울만한 가치가 있다고 생각한다. 안드로이드 공식적으로 만든 Compose sample이 있어서 이걸 먼저 분석해보고 앱을 만들어 보려고 한다. 

    '개발 > Android' 카테고리의 다른 글

    Compose 상태  (0) 2024.04.06
    Android Compose Navigation(Jetsnack sample 분석)  (0) 2024.03.23
Designed by Tistory.