DEV Community

Sewon Ann
Sewon Ann

Posted on

안드로이드 개발팀 내 공통 기능 제공하는 방법에 대한 고민

나는 안드로이드 개발팀 내에서 공통 기능 제공을 맡고있다. 각 사업별 라이브러리 모듈이 필요로 하는 기능을 제공해야 한다.

다른 모듈에 기능을 제공하는 방법은 정말 다양하다. 내가 생각한 기능 제공 방법과, 그 중에서 AAC ViewModel 로 기능을 제공하려다 난관에 봉착한 얘기를 해보겠다.

생각나는대로 다른 팀, 모듈에 기능을 제공하는 방법을 생각해봤다.

  1. SDK : 가장 거창하고, 거대하고, 손이 많이 가는 방식이라고 볼 수 있겠다. SDK 라 하면 대부분 초기화 과정에서 온갖 옵션을 설정해서 SDK client 인스턴스를 만들어내고, 그 인스턴스가 제공하는 메서드들을 이용해 기능을 제공받는다. 그런데 같은 프로젝트 내부라면 SDK 는 너무 거창하고 불편하겠지.

  2. UseCase : SDK와 반대로, 생각해볼 수 있는 가장 작은 단위의 기능 제공 형태이다. 사용자는 Dagger 나 Koin 등의 DI 도구를 이용해 자신의 VM 등등에서 UseCase 를 주입받아 사용하고, UseCase 가 제공하는 단 하나의 펑션의 결과를 이용해 원하는 결과를 받는다. UI 관련 작업은 못하겠지.

  3. Repository : UseCase가 개별 기능을 제공한다면, Repository 는 몇개의 기능을 묶어서 제공한다. UserRepository, CarRepository 등등. 내가 가장 많이 제공하는 형태이다. DI를 통해 사용측에서 가져다 쓴다.

그럼 UI가 엮인 기능은 어떻게 제공할까? 커스텀 뷰 하나로 끝나는게 아니라 화면 전체를 제공해야 한다면 Activity 나 Fragment 를 제공해야 한다. 사용하는 측에선 startActivity 를 하거나, 자신의 Activity 에서 Fragment 를 add해서 활용한다. Activity의 경우엔 호출할 때 intent 에 몇가지 인자를 넣어서 입맞에 맞게 동작을 바꿀 순 있지만 제약이 많고, Fragment 의 경우엔 Fragment 의 함수를 호출할 순 있지만, 안드로이드 특성 상 시스템이 Fragment를 언제 재생성할 지 모르기 때문에 안전하게 만든다면 Activity와 마찬가지로 Fragment 의 argument 를 이용하는 수준에서 커스터마이징할 수 밖에 없다.

굉장히 복잡한 로직이 담긴 다이얼로그를 제공하게 되었는데, 어떻게 제공할 수 있을까 고민했다. 쓰는 쪽에서도 내부 로직 일부를 커스텀해야 하기 때문에 단순 intent 나 argument bundle 수준으로 대응할 수 없었다.

그럼 ViewModel 과 Fragment 를 함께 제공하면 어떨까? 라는 생각에 도달했다. Fragment 는 UI 를 제공하고, ViewModel 을 통해서 기능을 제공하고, 필요한 경우 로직을 커스터마이징 한다. ViewModel 은 사용하는 측의 Activity/ Fragment 에서 생성하고, 내가 만든 Fragment 는 그 ViewModel 을 그대로 사용하면 된다. activity 레벨의 ViewModel로 만들면 가능하다.

로직을 커스터마이징해야 한다면, 인터페이스의 구현체를 직접 넣으라고 만들면 된다. 대충 이런 식이다. Koin 을 쓴다고 가정한다.

//사용측
class CustomerActivity {
  val featureViewModel by viewModel<FeatureViewModel> {
      parametersOf( CustomLogicImpl() )
  }

  onCreate() {
     featureViewModel.someLiveData.observe() {...}

     FeatureDialogFragment().show()
  }
}

//기능 제공측
class FeatureDialogFragment {
  val featureViewModel by sharedViewModel<FeatureViewModel>()

  onCreate() {
      featureViewModel.doSomething()  
  }
}
Enter fullscreen mode Exit fullscreen mode

이렇게 하니, 사용측에서 요구하는 로직 커스터마이징도 지원하고, UI도 제공할 수 있었다.

하지만 위 사용측 코드는 자칫 잘못하면 메모리릭이 나기 쉽다. 예를 들어 위 코드를 아래와 같이 익명 내부 클래스로 짜게 되면, 액티비티 메모리릭이 날 수 있다. 그 이유는 액티비티보다 수명이 긴 뷰모델이 액티비티에 대한 참조를 들고 있기 때문이다.

class CustomerActivity {
  val featureViewModel by viewModel<FeatureViewModel> {
      parametersOf( object: CustomLogic { ...} ) //이러면 못써!
  }
}

Enter fullscreen mode Exit fullscreen mode

어제부터 이 문제로 고민하고 있는데, 현재 내린 결론은 그냥 저런 방식으로 기능을 제공하면 잠재적인 문제가 발생할 소지가 많으니 하지 말자이다. 만약 위와 같이 UI 도 제공하고, 커스터마이징 가능한 로직도 제공해야 한다면 어떻게 해야 할까? 지금 드는 생각은 그냥 abstract Fragment 와 abstract ViewModel 을 제공해서, 사용측에서 알아서 커스터마이징 부분을 채워넣으라고 하는 방법 밖에 모르겠다. 라이프사이클이 얽혀들어가면 모든 문제가 백배 어려워지는 듯. ㅠㅠ

참고로 하나의 DI 컨테이너 안에서 돌아가는 경우라면 scope을 잘 조정해서 원하는 ViewModel 구현체를 Fragment 에서 주입받도록 구현할 수도 있다. Koin의 경우 Qualifier를 사용하면 된다. 이 경우엔 ViewModel 을 abstract하게 만들어서 알아서 구현부분을 채워넣으라고 하고, 이 ViewModel을 Fragment에서 사용하면 된다. 하지만 내 프로젝트의 경우엔 한 프로젝트 내부에서 여러개의 Koin Application을 사용하기에, 단순한 Qualifier로도 해결이 되지 않아 이 방식도 쓰지 못한다. 어떻게 어떻게 해결을 한다손 치더라도 배보다 배꼽이 더 커질 것 같아 포기했다.

Top comments (0)