Android 앱 아키텍처, ViewModel, LiveData

jeunguri·2022년 5월 5일
0

android

목록 보기
5/13


아키텍처를 사용하면 좋은 이유

  • 일관적인 코드 작성으로 유지보수, 협업 장점
  • 생산성 향상
  • 테스트의 용이성
  • 어플리케이션 개발의 방향성을 잡아줌



Android Google 권장 앱 아키텍처

각 애플리케이션에는 레이어가 두 개 이상 있어야 한다.

  • 화면에 애플리케이션 데이터를 표시하는 UI Layer
  • 앱의 비즈니스 로직을 포함하고 애플리케이션 데이터를 노출하는 Data Layer
  • UI와 Data 레이어 간 상호작용을 최소화하고 재사용하기 위한 Domain Layer



UI 레이어

UI 레이어의 역할은 화면에 애플리케이션 데이터를 표시하는 것이다. 사용자 상호작용(ex: 버튼 누르기) 또는 외부입력(ex: 네트워크 응답)으로 인해 데이터가 변할 때마다 변경사항을 반영하도록 UI가 업데이트되어야 한다.

UI 레이어는 다음 두가지로 구성된다.

  • 화면에 데이터를 렌더링하는 UI 요소
  • 데이터를 보유하고 이를 UI에 노출하며 로직을 처리하는 상태 홀더 (ex: ViewModel 클래스)


Data 레이어

Data 레이어에는 비즈니스 로직이 포함되어 있다.

Data 레이어는 0개부터 여러 개의 데이터 소스를 각각 포함할 수 있는 저장소로 구성된다. 앱에서 처리하는 다양한 유형의 데이터마다 저장소 클래스를 만들어야 한다. 예를 들어 영화 관련 데이터는 MoviesRepository 클래스, 결제 관련 데이터에는 PaymentsRepository 클래스를 만들 수 있다.

저장소 클래스에서 담당하는 작업은 다음과 같다.

  • 앱의 나머지 부분에 데이터 노출
  • 데이터 변경사항을 한 곳에 집중
  • 여러 데이터 소스 간의 충돌 해결
  • 앱의 나머지 부분에서 데이터 소스 추상화
  • 비즈니스 로직 포함



ViewModel

ViewModel 클래스는 수명 주기를 고려하여 UI 관련 데이터를 저장하고 관리하도록 설계되었다.
ViewModel 클래스를 사용하면 화면 전환과 같이 구성을 변경할 때도 데이터를 유지할 수 있다.

UI 컨트롤러 로직에서 뷰 데이터 소유권을 분리하는 방법이 쉽고 효율적인 이유는 아래와 같다.

  • 시스템에서 UI 컨트롤러를 제거하거나 다시 만드는 경우, 저장된 모든 UI 관련 데이터가 삭제된다.
  • UI 컨트롤러가 반환하는 데 시간이 걸릴 수 있는 비동기 호출을 자주 해야 한다. UI 컨트롤러는 비동기 호출을 관리해야 하며 관리에는 많은 유지관리가 필요하다. 구성 변경 시 개체가 다시 생성되는 겨우 개체가 이미 수행된 호출을 다시 호출해야 할 수 있으므로 리소스가 낭비된다.
  • UI 컨트롤러에 과도한 책임을 할당하면 다른 클래스로 작업이 위임되지 않고, 단일 클래스가 혼자 앱의 작업을 모두 처리하려 한다.



LiveData

LiveData는 Data의 변경을 관찰할 수 있는 Data Holder 클래스이다.
Activity나 Fragment, 서비스 등과 같은 안드로이드 컴포넌트의 생명주기를 인식하며 그에따라 LiveData는 활성상태(active)일때만 데이터를 업데이트(Update) 한다.
*활성상태란? STARTED 또는 RESUMED를 의미한다.

또한, LiveData 객체는 Observer 객체와 함께 사용된다. LiveData가 가지고 있는 데이터에 변화가 일어날 경우, LiveData에 등록된 Observer 객체에 변화를 알려주고, Observer의 onChanged() 메서드가 실행되게 된다.

LiveData의 장점

  • Data와 UI간 동기화
    LiveData는 Observer 패턴을 따른다. 따라서 Observer 객체를 사용해 데이터의 변화가 일어나는 곳마다 매번 UI를 업데이트하는 코드를 작성할 필요 없이 통합적이고 확실하게 데이터의 상태와 UI를 일치시킬 수 있다.
  • 메모리 누수 없음
    Observer 객체는 생명주기 객체와 결합되어 있어 컴포넌트가 Destroy될 경우 메모리상에서 스스로 해제한다.
  • Stop 상태의 액티비티와 Crash가 발생하지 않음
    Observer의 생명주기가 inactive(비활성화)일 경우, Observer는 LiveData의 어떤한 이벤트도 수신하지 않는다.
  • 생명주기에 대한 추가적인 handling을 하지 않아도 된다.
    LiveData가 생명주기에 따른 Observing을 자동으로 관리해주기 때문에 UI컴포넌트는 관련 있는 데이터를 관찰하기만 하면 된다.
  • 항상 최신 데이터를 유지한다.
  • 화면 구성이 변경되어도 데이터를 유지한다.
    예를들어, 디바이스를 세로에서 가로로 화면 변경시켜도 LiveData는 회전하기 전의 최신 상태를 즉시 받아온다.
  • 자원(Resource) 공유
    LiveData를 상속하여 자신만의 LiveData클래스를 구현할 수 있고, Singleton 패턴을 이용해 시스템 서비스를 둘러싸면 앱 어디에서나 자원을 공유할 수 있다.



ViewModel, LiveData 사용한 예제


NumberViewModel

class NumverViewModel : ViewModel() {}

ViewModel 클래스를 상속하는 서브 클래스를 정의한다.



MainActivity

class MainActivity : AppCompatActivity() {
	lateinit var numberViewModel: NumberViewModel

    	override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        numberViewModel = ViewModelProvider(this).get(NumberViewModel::class.java)

ViewModel을 생성하기 위해서는 ViewModel Provider 객체가 필요하다. 매개변수에는 lifecycle을 가지고 있는 것을 넣어준다. (즉, 자기 자신)

그리고 가져오고 싶은 ViewModel 클래스를 넣어 ViewModel을 가져온다.

메인 액티비티가 실행되고 나면 ViewModel Provider를 통해 ViewModel을 가져오면서 생성자가 호출된다.



NumberViewModel

class NumberViewModel : ViewModel() {
	
    // 내부에서 설정하는 자료형은 mutable로 변경 가능하도록 설정
	private val _currentValue = MutableLiveData<Int>()
    
    // 변경되지 않는 데이터를 가져올 땐 이름을 _언더스코어 없이 설정
    val currentValue: LiveData<Int>
        get() = _currentValue
     
    // 초기값 설정
    init {
        _currentValue.value = 0
    }
    
    // ViewModel이 가지고 있는 값을 변경하기 위해 updateValue 메서드를 구현
    fun updateValue(actionType: ActionType, input: Int) {
        when(actionType) {
            ActionType.PLUS ->
                _currentValue.value = _currentValue.value?.plus(input)
            ActionType.MINUS ->
                _currentValue.value = _currentValue.value?.minus(input)
        }
    }
}

생성자가 호출되면서 ViewModel이 가지고 있는 Mutable 데이터의 값을 설정한다.



MainActivity

class MainActivity : AppCompatActivity(), View.OnClickListener {

    private lateinit var binding: ActivityMainBinding

    lateinit var numberViewModel: NumberViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
     
        numberViewModel = ViewModelProvider(this).get(NumberViewModel::class.java)
 
        numberViewModel.currentValue.observe(this, Observer {
            binding.numberTextVIew.text = it.toString()
        })

        // 리스너 연결
        binding.plusButton.setOnClickListener(this)
        binding.minusButton.setOnClickListener(this)
    }

    // 클릭
    override fun onClick(view: View?) {
        val userInput = binding.inputNumberEditText.text.toString().toInt()

        // ViewModel에 LiveData 값을 변경하는 메서드 실행
       when(view) {
           binding.plusButton ->
                numberViewModel.updateValue(actionType = ActionType.PLUS, userInput)
           binding.minusButton ->
                numberViewModel.updateValue(actionType = ActionType.MINUS, userInput)
        }
    }
}

그리고 나서 메인 액티비티쪽에서 plusButton을 누룰 때 ViewModel이 가지고 있는 updateValue() 메서드를 실행한다. 그렇게 되면 현재 Mutable 데이터가 가지고 있는 값에 input만큼 값을 추가해주게 된다.

값이 변경됐을 때 ViewModel이 가지고 있는 LiveData가 변경되었을 때 값을 받겠다는 것을 observe() 메서드를 통해 설정한다.

numberViewModel.currentValue.observe(this, Observer {
            findViewById<TextView>(R.id.numberTextVIew).text = it.toString()
        })

currentValue가 값이 변경됐을 때마다 화면에 적용이 된다.





참고

0개의 댓글