본문 바로가기
안드로이드

[안드로이드] 보일러 플레이트 (상용구 코드)

by algosketch 2021. 12. 31.

사소한 기능을 구현하려고 해도 꽤 긴 코드를 작성해야 하는 불편함이 있다. 또 이런 기능을 여러 군데에서 구현하려고 하면 같은 코드를 일부만 변형시켜서 중복 사용하게 된다. 이렇게 반복되는 코드를 보일러 플레이트 혹은 상용구 코드라고 부른다.

안드로이드에서의 보일러 플레이트 코드는 대표적으로 Activity, Fragment, ViewModel, UseCase 등이 있다. 이 글에서는 Fragment 로 예를 들어 설명한다. 마찬가지로 이 프로젝트 코드를 기반으로 설명할 예정이다. 이 프로젝트 또한 프로젝트를 처음 시작할 때마다 설계와 관련된 코드를 작성해야 하는 불편함을 줄이기 위해 시작했다. star 와 fork 해 주시면 감사합니다.

 

class MainFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_main, container, false)
    }
}

Fragment 를 만들어서 화면에 렌더링하기 위해서는 위와 같은 코드를 작성해야 한다. 생각보다 길지 않은 코드일 수 있다. 예제를 단순화하기 위해 가장 간단한 코드를 가져왔지만 데이터 바인딩이 추가되고 ViewModel 의 이벤트를 구독하는 등 여러 코드가 추가되면 꽤 복잡해질 수 있다.

안드로이드에서는 관례적으로 BaseFragment 를 만들고 이를 상속하는 형태로 Fragment 를 관리한다. 여기서 설명하지 않은 Activity, ViewModel, UseCase 등도 비슷한 방법으로 관리할 수 있다.

abstract class BaseFragment : Fragment() {
    abstract val layoutResourceId: Int

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(layoutResourceId, container, false)
    }
}

보일러 플레이트 코드를 미리 작성해 놓으면 그 다음부턴 이것을 상속받아 사용할 수 있다.

class MainFragment : BaseFragment() {
    override val layoutResourceId = R.layout.fragment_Main
}

단 세 줄만으로 기존 코드를 대체할 수 있다. 아래는 데이터 바인딩과 초기 값 설정 등을 고려하여 만든 실제 사용중인 코드이다.

abstract class BaseFragment<VB: ViewDataBinding> : Fragment() {
    protected lateinit var binding: VB
    abstract val layoutResourceId: Int

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = DataBindingUtil.inflate(inflater, layoutResourceId, container, false)
        binding.lifecycleOwner = this

        return binding.root
    }

    open fun initState() {

    }

    abstract fun initDataBinding()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        initState()
        initDataBinding()
    }
}

데이터 바인딩과 관련된 코드가 추가 되었다. initState 는 초기 값 설정을 위한 메소드이다. Flutter 의 LifeCycle 메소드에서 이름을 따 왔다. (React 에는 setState 와 같은 훅이 존재한다.) 네트워크 요청이나 데이터베이스에 접근하는 것은 오래 걸리는 일이다. ANR(Application Not Responding, 5초이상 멈추면 애플리케이션이 비정상 종료됨.) 이 발생할 수도 있다. 이와같은 요청을 통해 (비동기적으로)데이터를 가져오고 데이터에 따라 UI 를 렌더링 해야하는 경우 이 메소드를 이용할 수 있다.

initState 는 open 으로 지정했으나 initDataBinding 은 abstract 로 지정해 자식 클래스에서 반드시 구현하도록 만들었다. 데이터 바인딩은 MVVM 패턴을 사용한다면 반드시 이루어져야 하는데, 실수로 바인딩을 하지 않는 실수를 방지하기 위해 abstract 키워드를 붙여주었다. (인프런에서 유명한 개발자 김영한님이 에러 중 가장 좋은 에러는 컴파일 에러라 하였다) 모든 Fragment 에서 DB 나 네트워크 요청이 필요하지는 않기에 initState 는 open 키워드를 사용했다.