Unit 3: Navigation (5)

quokka·2021년 11월 13일
0

Android Basics in Kotlin

목록 보기
14/25
post-thumbnail

App Architecture

잘 디자인된 앱 아키텍처는 향후 앱 확장과 팀 공동작업에 좋다.

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

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

모델에서 UI 만들기
말 그대로 모델에서 UI를 만들어야 한다는 원칙. (지속적인 모델 권장)
모델은 데이터 처리를 담당하는 구성요소로, 앱의 뷰 객체나 앱 구성요소와 독립되어 있으므로 앱의 수명 주기 및 관련된 문제의 영향을 받지 않는다.

아키텍처의 구성요소

안드로이드의 기본 클래스 또는 구성요소는 UI 컨트롤러 (Activity/Fragment), ViewModel, LiveData, Room이다.

UI 컨트롤러 (Activity / Fragment)

UI 컨트롤러는 데이터를 display하거나 사용자 이벤트나 사용자 상호작용을 포착하여 UI를 제어한다.

✔ 데이터나 데이터 관련 의사 결정 로직은 UI 컨트롤러에 포함되어서는 안된다.
(안드로이드 시스템이 필요시 언제든 UI 컨트롤러를 제거할 수 있기 때문에 앱 데이터나 상태를 저장하면 안된다.)

→ 대신 데이터 관련 의사 결정 로직은 ViewModel에 포함한다.

ViewModel

뷰에 표시되는 앱 데이터의 "모델". Activity나 Fragment가 소멸되고 다시 생성될 때 폐기되지 않는 데이터를 저장한다.

✔ 뷰 계층 구조에 액세스하거나 activity나 fragment의 참조를 보유하면 안된다.

Unscramble

여기에서 unscamble app의 starter 틀을 클론한다.

앱 개요
Scramble 된 단어를 맞추는 게임. 10번 진행하는데 답을 모르면 skip할 수 있고, 틀리면 다시 입력할 수 있다. 게임이 끝나면 점수와 함께 Exit와 Play Again 버튼이 뜬다.

GameViewModel

1. dependencies

ViewModel을 사용하려면 먼저 라이브러리 종속 항목을 추가한다. ( latest version 확인 )

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'

2. ViewModel을 프래그먼트에 연결하기

GameFragment의 상단에 다음과 같이 GameViewModel 유형의 속성을 추가한다.

private val viewModel: GameViewModel by viewModels()

여기서 by viewModels()를 속성 위임이라고 한다.

Kotlin property delegate 속성 위임

Kotlin 속성 위임을 사용하면 getter-setter 책임을 다른 클래스에 넘길 수 있다.
대리자 클래스는 by로 정의할 수 있고, 대리자 클래스는 속성의 getter, setter 함수를 제공하고 변경사항을 처리한다.

// Syntax for property delegation
var <property-name> : <property-type> by <delegate-class>()

속성 위임을 하지 않고, private val viewModel = GameViewModel() 이런식으로 뷰 모델을 초기화하는 경우 기기를 회전하는 등의 기기에서 구성이 변경될 때 앱이 viewModel 참조 상태를 손실하게 된다.

속성 위임을 통해서 viewModel 객체의 칙임을 viewModels라는 별도의 클래스에 위임한다. 대리자 클래스는 첫 번째 액세스 시 자동으로 viewModel 객체를 만들고, 이 값을 유지하다 요청이 있을 때 반환한다.

3. 내용 옮기기

GameViewModel을 추가해 GameFragment에 있는 데이터와 관련된 내용을 옮긴다.

그림처럼 MainActivityGameFragment가 포함되어 있고, GameFragmentGameViewModel에 있는 게임 관련 정보에 액세스한다.

class GameViewModel : ViewModel() {
}

GameViewModel은 추상클래스 ViewModel을 확장한다.

Backing property

ViewModel 내에서는 데이터를 수정할 수 있어야 하므로 데이터는 private 및 var다. 하지만 외부에서는 데이터를 읽을 수 있지만 수정할 수는 없어야 한다. 이때 backing property를 사용한다.

private var _count = 0
val count: Int get() = _count

private var_count는 내부에서 수정할 수 있지만 외부에서 접근할 수 없고, public valcount는 외부에서 읽을 수만 있다.

❗ ViewModel 내부의 변경 가능한 데이터는 항상 private이어야 한다!

Unscramble 앱 사용 예

GameViewModelcurrentScrambledWord를 backing property를 사용해 선언한다.

private var _currentScrambledWord = "test"
val currentScrambledWord: String get() = _currentScrambledWord

GameFragment에서 currentScrambledWord 데이터값을 사용하려면 viewModel.currentScrambledWord 형태로 사용할 수 있다.

ViewModel 수명 주기

프레임워크는 activity이나 fragment의 범위가 유지되는 동안 ViewModel을 유지한다.

GameViewModelGameFragment에 Log를 추가해 동작을 확인할 수 있다.

  • 앱을 실행하면 GameFragmentGameViewModel이 생성된다.

  • 기기 화면을 회전하면 GameFragment는 매번 소멸되고 다시 생성되지만 GameViewModel은 한 번만 생성되고 기기 회전으로 소멸되지 않는다.

  • 앱에서 나가면 GameViewModel이 소멸되고 콜백 onCleared()가 호출되고, GameFragment가 소멸된다.

MaterialAlertDialog

Material design 구성요소 라이브러리인 MaterialAlertDialog를 사용해 대화상자를 띄울 수 있다.

    MaterialAlertDialogBuilder(requireContext())
            .setTitle(getString(R.string.congratulations))
            .setMessage(getString(R.string.you_scored, viewModel.score))
            .setCancelable(false)
            .setNegativeButton(getString(R.string.exit)) { _, _ ->
                exitGame()
            }
            .setPositiveButton(getString(R.string.play_again)) { _, _ ->
                restartGame()
            }
            .show()

setNegativeButton(getString(R.string.exit)) { _, _ -> exitGame() } 이런 구문은 후행 람다 구문이라고 한다...?



솔루션 코드

0개의 댓글