안드로이드/안드로이드 Kotlin

[Android Basics in Kotlin] Navigation: 아키텍처 구성요소 / 뷰모델 / LiveData / dataBinding

요빈 2023. 3. 15. 23:20

https://developer.android.com/courses/android-basics-kotlin/unit-3

 

Android Kotlin Basics in Kotlin  |  Android Basics in Kotlin - Navigation  |  Android Developers

Enhance your users’ ability to navigate across, into and back out from the various screens within your app for a consistent and predictable user experience.

developer.android.com

해당 글은 위 사이트의 PATHWAY 3 과정인 아키텍처 구성요소를 공부하며 작성한 글입니다.

이 과정에서는 테스트 및 유지관리할 수 있는 앱을 디자인하도록 돕는 라이브러리 모음인 Android Jetpack 아키텍처 구성요소를 사용하는 방법을 배웁니다.


 

앱 아키텍처

 

Android Jetpack 라이브러리에 포함된 Android 아키텍처 구성요소는 효율적인 아키텍처로 앱을 디자인하는 데 도움을 주며, 앱 아키텍처를 안내하는 역할을 한다. 앱 아키텍처는 일련의 디자인 규칙으로 앱의 구조를 제시한다.

훌륭한 앱 아키텍처를 사용하면 유연하고 확장 가능하며, 향후 유지관리가 가능한 강력한 코드를 만들 수 있다.

 

가장 일반적인 아키텍처 원칙은 관심사 분리, 모델에서 UI 만들기이다.

 

관심사 분리

관심사 분리 디자인 원칙은 각각 별개의 책임이 있는 여러 클래스로 앱을 나눠야 한다는 원칙이다.

 

모델에서 UI 만들기

모델은 앱의 데이터 처리를 담당하는 구성요소로, 앱의 뷰 객체 및 구성요소와 독립되어 있으므로 앱의 수명주기 및 관련 문제에 영향을 받지 않는다. Android 아키텍처의 기본 클래스 또는 구성요소는 UI 컨트롤러(활동/프래그먼트), ViewModel, LiveData, Room이다.

이러한 구성요소는 수명 주기의 복잡성을 어느정도 처리하므로 수명 주기 관련 문제를 피하는 데 도움이 된다.

 

다음 그림은 아키텍처의 기본적인 부분을 보여준다.

UI 컨트롤러(활동/프래그먼트)

UI 컨트롤러는 화면에 뷰를 그리고 사용자 이벤트나 사용자가 상호작용하는 다른 모든 UI관련 동작을 캡쳐해 UI를 제어한다.

앱의 데이터 또는 데이터에 관한 모든 의사 결정 로직은 UI 컨트롤러 클래스에 포함되어서는 안된다.

Android 시스템은 사용자와의 상호작용 또는 메모리 부족과 같은 시스템 조건으로 인해 언제든지 UI 컨트롤러를 제거할 수 있다.

이러한 이벤트는 개발자가 제어할 수 없기 때문에 UI 컨트롤러에 앱 데이터나 상태를 저장해서는 안된다.

대신 데이터 의사 결정 로직은 ViewModel에 추가한다.

 

ViewModel

ViewModel은 뷰에 표시되는 앱 데이터의 모델이다.

모델은 앱의 데이터 처리를 담당하는 구성요소로 아키텍처 원칙에 따라 모델에서 UI가 도출되는 앱을 만들 수 있다.

ViewModel은 Android 프레임워크에서 활동이나 프래그먼트가 소멸되고 다시 생성될 때 폐기되지 않는 앱 관련 데이터를 저장한다.

ViewModel 객체는 구성이 변경되는 동안 자동으로 유지되어(인스턴스처럼 소멸되지 않음) 보유하고 있는 데이터를 다음 뷰에 즉시 사용할 수 있다.

 

ViewModel

 

ViewModel을 사용하는 과정은 다음과 같다.

 

1. ViewModel 관련 의존성 추가

 

build.gradle(app) 파일 내 의존성 블록 내 ViewModel 라이브러리 항목을 추가한다.

// ViewModel
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'

2. ViewModel 클래스 생성

 

클래스 명을 ~ViewModel로 지정하고, ViewModel 추상 클래스를 상속받는다.

class GameViewModel : ViewModel() {
}

3. ViewModel과 프래그먼트 연결

 

ViewModel을 UI 컨트롤러(활동/프래그먼트)에 연결하려면 UI 컨트롤러 내에 ViewModel에 관한 참조(객체)를 만든다.

해당 과정에서는 프래그먼트 내에 ViewModel의 객체 인스턴스를 만든다.

Kotlin 속성 위임(by viewModles())을 사용해 ViewModel을 초기화한다.

private val viewModel: GameViewModel by viewModels()

* Kotlin 속성 위임

 

코틀린에는 var(변경 가능) 속성에 자동으로 getter, setter 함수가 생성되어, 값을 할당하거나 속성 값을 읽을 때 사용한다.

읽기 전용 속성(val)의 경우는 기본적으로 getter 함수만 생성된다.

 

코틀린에서 속성 위임을 사용하면 getter-setter 책임을 다른 클래스에 넘길 수 있다.

그 다른 클래스(대리자 클래스라고 함)는 속성의 getter, setter 함수를 제공하고 변경 사항을 처리한다.

대리자 속성은 by절 및 대리자 클래스 인스턴스를 사용해 정의된다.

 

속성위임을 사용하지 않으면 기기에서 구성이 변경되는 동안 앱이 viewModel 참조의 상태를 손실하게 된다. 

대신 속성 위임 접근 방식을 사용해 ViewModel 객체의 책임을 ViewModels라는 별도의 클래스에 위임한다.

즉, ViewModel에 액세스하면 이 객체는 대리자 클래스인 ViewModels에 의해 내부적으로 처리된다.

대리자 클래스는 첫 번째 액세스 시 자동으로 viewModel 객체를 만들고 이 값을 구성 변경 중에도 유지했다가 요청이 있을 때 반환한다.

 

4. ViewModel 데이터 처리

 

ViewModel 내에서는 데이터를 수정할 수 있어야 하므로 데이터는 private  var이어야 한다. 

ViewModel 외부에서는 데이터를 읽을 수 있지만 수정할 수는 없어야 하므로 데이터는 public  val로 노출되어야 한다.

코틀린의 지원 속성이라는 기능을 이용해 위 내용을 구현할 수 있다.

// 선언된 클래스 내에서만 수정될 수 있도록 private & var로 선언
private var _count = 0

// getter 메소드를 오버라이드해 private 변수의 값을 반환한다.
val count: Int
   get() = _count

 

ViewModel의 수명주기

 

프레임워크는 활동이나 프래그먼트(UI 컨트롤러)가 유지되는 동안 ViewModel을 유지한다.

ViewModel은 소유자(UI)가 화면 회전과 같은 구성 변경으로 인해 소멸되는 경우에도 소멸되지 않는다.

소유자의 새 인스턴스는 다음 다이어그램과 같이 기존 ViewModel 인스턴스에 다시 연결된다.

LiveData

LiveData는 수명 주기를 인식하는 관찰 가능한 데이터 홀더 클래스이다.

LiveData의 특성은 다음과 같다.

 

   - LiveData는 데이터를 보유하며, 모든 유형의 데이터에 사용할 수 있는 wrapper이다.

   - LiveData는 관찰 가능하다. 즉, LiveData 객체에서 보유한 데이터가 변경되면 관찰자에게 알림이 간다.

   - LiveData는 수명주기를 인식한다. LiveData는 STARTED 또는 RESUMED 같은 활성 수명 주기 상태인 관찰자만 업데이트한다.

 

MutableLiveData는 변경 가능한 버전의 LiveData이다. 즉, 내부에 저장된 데이터의 값을 변경할 수 있다.

LiveData 및 MutableLiveData는 일반 클래스이므로 클래스에 보유되는 데이터의 유형을 지정해야 한다.

private val liveDataString = MutableLiveData<String>()

 

LiveData 객체 내 데이터에 액세스하려면 value 속성을 사용한다. 

 

LiveData 객체에 관찰자 연결

LiveData 객체가 보유한 데이터가 변경되면 해당 데이터를 사용하는 UI인 관찰자에게 알림이 간다. 

즉, 앱 구성요소 GameFragment에서 관찰자를 설정해 앱 데이터 currentScrambleWord의 변경사항을 관찰한다.

LiveData는 수명주기를 인식하기 때문에 GameFragment의 상태가 STARTED, RESUMED 상태인 경우에만 알림을 받는다.

 

LiveData 객체에 관해 observe 메서드를 사용해 객체를 관찰할 수 있다.

관찰자는 보통 onViewCreated()에서 구현한다. (프래그먼트의 경우)

observe(viewLifecycleOwner, observer) 메서드에는 두 매개변수가 있다.

 

첫 번째 매개변수는  viewLifecycleOwner이다. viewLifecycleOwner는 프래그먼트 뷰의 수명 주기를 나타내며 이 매개변수를 통해

LiveData가 프래그먼트의 수명 주기를 인식해 활성상태일 때만 관찰자에게 알릴 수 있다.

 

두 번째 매개변수는 observer이다. observer는 람다 표현식 형태로 전달된다.

LiveData에 변경이 감지되면 해당 익명 함수가 실행된다.

// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })

 

데이터 결합

뷰 결합(Binding)은 뷰에 더 쉽게 액세스할 수 있는 기능으로, 각 XML 레이아웃 파일의 결합 클래스를 생성한다.

결합 클래스의 인스턴스에는 상응하는 레이아웃에 ID가 있는 모든 뷰의 직접 참조가 포함된다.

뷰 결합을 사용하면 뷰(레이아웃 파일)에서 앱 데이터를 참조할 수 없기 때문에 데이터 결합을 사용하게 된다.

 

데이터 결합은 선언적 형식을 사용해 레이아웃의 UI 구성요소를 앱의 데이터 소스에 결합한다.

간단히 말해 데이터 결합은 코드에서 데이터를 뷰 + 뷰 결합에 결합(뷰를 코드에 결합)하는 것이다.

--> 레이아웃 뷰가 앱 데이터에 액세스할 수 있다!

--> LiveData에 observe() 메서드를 통해 관찰하지 않고, 레이아웃 파일 내에서 변경된 데이터를 인식해 적용한다는 것!

 

UI 컨트롤러에서 뷰 결합 사용의 예

binding.textViewUnscrambledWord.text = viewModel.currentScrambledWord

레이아웃 파일에서 데이터 결합 사용의 예

android:text="@{gameViewModel.currentScrambledWord}"

 

위 코드는 데이터 결합 라이브러리를 사용해 레이아웃 파일에서 뷰/위젯에 직접 앱 데이터를 할당하는 방법이다.

할당 표현식에는 @{} 구문이 사용된다.

 

데이텨 결합의 주요 이점은 활동에서 많은 UI 프레임워크 호출을 삭제할 수 있어 파일이 더욱 단순해지고 더 쉬운 유지관리가 가능하다.

또한 앱 성능이 향상되며 메모리 누수 및 null 포인터 예외를 방지할 수 있다.

 

결합 표현식은 레이아웃 내에서 레이아웃 속성을 참조하는 속성(ex. android:text)에서 작성된다.

레이아웃 속성은 <variable>태그를 통해 데이터 결합 레이아웃 파일의 상단에 선언된다.

종속 변수 중 하나라도 변경되면 'DB 라이브러리'가 결합 표현식을 실행하고 이에 따라 뷰를 업데이트한다.

 

결합 표현식은 @ 기호로 시작하고 중괄호로 래핑된다. 

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}" />

 

요약