MVVM은 사용자 UI/UX를 담당하는 V, V에 보이는 데이터를 담는 M, 그리고 그 V와 M 사이 양방향 바인딩을 책임지는 VM으로 구성됩니다.
MVVM의 VM은 양방향 바인딩이기 때문에 단방향 바인딩인 MVC의 C와 달리 V가 바뀌거나 M이 바뀌는 경우, 바인딩된 V와 M도 즉시 바뀌어야 합니다.
그러면서도 V는 UI와 UX에 관한 책임만을 져야 하기 때문에 V가 M을 어떻게 가져오는지에 대한 책임은 VM이 져야합니다.
이때 M은 서버일 수도 있고, 블루투스 기기일 수도 있고, 스마트폰에 저장된 파일일 수도 있습니다.
타입에 상관없이 MVVM에서는 이를 전부 M으로 보고, VM은 M에 접근해서 POJO형태로 가공한 뒤 V에 넘겨줍니다.
POJO(Plain Old Java Object): 필드, 접근자, 생성자 밖에 없는 데이터를 표현하기 위한 목적만 가진 Class. 아리의 DTO들이 이에 해당함.
따라서 VM은 접근해야하는 M의 종류만큼 접근에 필요한 서비스객체를 주입받아야 합니다. 어떤 VM이 서버, 블루투스, 그리고 파일입출력 전부 이용한다면, 3개 서비스 객체 전부 VM에 주입되어야 합니다.
주입: 타 클래스 인스턴스를 멤버변수로 사용하는 것
또한 각 서비스 객체가 멀티스레딩에 따른 교착상태에 빠지지 않도록 해야하고, 메모리 누수가 발생하지 않도록 해야하기 때문에 서비스 객체의 생성-소멸 시점을 전부 VM이 책임져야 합니다.
이 개념을 의존성주입(Dependency Injection)이라고 합니다.
아리 앱에서는 단순하게 서비스를 싱글톤으로 단 한 개씩만 생성하며, 필요한 VM에서는 생성자에서 명시적으로 주입하여 사용합니다.
서비스 생성주기는 어플리케이션과 동일하고, VM끼리 같은 서비스 인스턴스를 공유할 수 있어 서버와의 통신에 사용하는 jwt 키값 설정이나, 블루투스 연결을 매 VM별로 할 필요가 없습니다.
또한 VM의 생성자를 보고 몇 개 서비스가 주입되어있는지 알 수 있어서 해당 VM의 책임범위를 명시적으로 관리할 수 있습니다.
서비스는 M을 가져오는 방법을 구현한 클래스입니다. 안드로이드에서는 메인 스레드에서 네트워크 등 통신작업을 할 수 없고, ms단위 이상의 작업은 백그라운드 스레드에서 처리하도록 권장하고 있습니다.
따라서 아리의 서비스에서 M은 전부 Observer 패턴으로 제공합니다.
Observer 패턴으로 Observe 대상 객체가 특정 행동을 했을 때 원하는 함수를 실행시키도록 프로그래밍할 수 있습니다.
다음 소스코드는 서버로부터 값을 가져오는 예시입니다.
언제가 될지는 모르지만 이 서비스가 값을 가져왔을 때 이를 화면에 보여주도록 하는 함수를 실행시키도록 User서비스를 Observe할 수 있습니다.
viewModel.getAllUser()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::printUsers, Throwable::printStackTrace);
Observe패턴을 이용함으로써 백그라운드 스레드에서 M을 가져오면서도, V에서는 이를 신경쓰지 않고, 어떻게 가공할 지에만 집중할 수 있습니다. - 예시에서는 this::printUsers
블루투스 또한 마찬가지입니다.
다음 소스코드는 ui에서 블루투스 기기목록을 검색하고 이를 사용자에게 보여줍니다.
viewModel.scanBle()
.subscribe(bleDeviceDTO -> Log.i("ble", bleDeviceDTO.getMacAddress()));
블루투스 기기목록을 검색하는 일은 VM의 역할입니다.
다음 소스코드는 블루투스 서비스를 이용하여 기기목록을 반환합니다.
public Observable<BleDeviceDTO> scanBle() {
return bleService.scanBle();
}
VM에서는 블루투스 서비스를 이용하여 블루투스 기기목록을 BleDeviceDTO 타입으로 가공해서 V에게 반환해줍니다.
이렇게 앞으로 아리 앱에서는 여러 액티비티와 그 액티비티별 뷰모델을 만들고, 뷰모델에 서비스를 주입해서 개발을 하면 되겠습니다.