일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 스크롤 탭
- base64 변환
- 기존 앱
- ios
- url 관찰
- swift #swift keychain #keychain 사용법
- List
- 개발자 면접
- convert base64
- 상단 탭바
- UIViewControllerTransitioningDelegate
- detect url
- notifychanged
- GeometryReader
- UIPresentationController
- Tuist
- DevelopmentRegion
- development language
- Side Menu
- swift
- Swift Package Manager
- pod install
- scrolling tab
- SwiftUI
- oberve url
- Android
- url 추적
- DataBinding
- ViewBuilder
- transformation.map
- Today
- Total
버그 잡이
[Udacity android with kotlin] 5. Architecture - viewmodel 본문
[Udacity android with kotlin] 5. Architecture - viewmodel
버그잡이 2020. 6. 15. 10:22
1. UI & ViewModel
(LiveData는 다음 시간에 다루기에 일단은 크게 두 분류로 나눈다.)
UI
- 자료를 UI에 표현
- 시스템이나 사용자가 보내는 이벤트를 수신
ViewModel
- UI에 보여줄 data를 가지고 있다 UI에 전달
- 어떤 data를 보여줄지 판단(데이터에 대한 계산을 여기서 한다)
(GameFragment의 예로 살펴보자)
- GameFragment는
- data를 UI에 나타내준다.
- button이 클릭된 경우 Fragment는 이 사실을 인식하고 viewmodel에게 알린다.
- GameViewModel은
- 점수, 단어 목록, 현재 단어 등에 대한 데이터를 가지고 있다.
- gameFragment에서 신호가 오면 관련된 data를 처리하고 data를 변경한다.(ui를 참조하여 직접 바꾸지는 않는다. ui를 참조하는 것은 fragment에서 한다.)
2. 왜 이런 번거로운 작업을 하는가? (왜 Viewmodel을 쓰는가?)
4강 lifycycle을 배우면서 configuration change 문제를 알아봤다. 화면 전환시, 홈스크린을 눌러 백그라운드로 갈 경우 데이터가 초기화 되는 문제이다. ViewModel은 이러한 문제를 해결해준다.
위 생명주기를 정확히 이해는 못 했지만 핵심은 configuration change시 viewmodel은 소멸되지 않기 떄문에 data를 가지고 있다 fragment가 재생성될때 data를 그대로 보여줄 수 있다는 것이다.
(물론, 백 버튼을 눌러 나간 경우는 viewmodel이 소멸된다)
UI, VIewModel에는 각각 어떤 것이 들어가야 하는가?
정답은 없다. 기본 원칙을 가지고 개발자의 판단에 맡긴다.
그렇다면 예제로 만들 스피드 퀴즈 앱을 통해서 한번 구성해보자.
스피드 퀴즈앱은 '몸으로 말해요'를 앱으로 구현한 것이다. 한 사람이 몸으로 단어를 설명하고 이를 맞추면 다음 단어로 넘기고 점수가 올라가고 문제를 다 맞추거나 정해진 시간이 다 되면 종료되며 최종 점수가 뜨는 구조의 앱이다.
(아래 문제를 풀어보자)
"다음 중 ViewModel에 들어갈 항목은?"
text나 score를 띄우는 행위는 ui에 대한 참조가 필요하기 때문에 -> UI
리스트를 갱신, 다음 단어로 뭘 선택할지 decision-making하는 것은 -> ViewModel
decision-making을 위한 기반 data도 -> ViewModel
(코드로 살펴보자)
GameFragment.kt
class GameFragment : Fragment() {
private lateinit var viewModel : GameViewModel
private lateinit var binding: GameFragmentBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
//뷰모델은 viewModelProvider를 통해서 생성해준다.
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
//버튼 클릭시 viewmodel에 신호를 보내고 viewmodel에서 처리된 데이터를 화면에 나타낸다.
binding.correctButton.setOnClickListener {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}
binding.skipButton.setOnClickListener {
viewModel.onSkip()
updateScoreText()
updateWordText()
}
updateScoreText()
updateWordText()
return binding.root
}
//업데이트된 단어와 점수를 화면에 표시하는 메서드이다.
//뷰를 참조하기 때문에 Fragment에 선언해주어야한다.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}
//게임을 끝내는 메서드 (이 부분은 다음 강좌에서 자세히 다루니 지금은 넘어가자)
private fun gameFinished() {
val action = GameFragmentDirections.actionGameToScore(viewModel.score)
findNavController(this).navigate(action)
}
}
GameViewModel.kt
class GameViewModel : ViewModel() {
// 현재 단어와 점수 data
var word = ""
var score = 0
// 단어 목록(여기서 하나를 선택해 현재 단어로 정함)
private lateinit var wordList: MutableList<String>
//처음 생성될때 단어 리스트를 섞어주고 단어를 하나 선태함과 동시에 리스트에서 지워준다
init{
Log.i("GameViewModel", "GameViewModel created!")
resetList()
nextWord()
}
/**
* Resets the list of words and randomizes the order
*/
private fun resetList() {
wordList = mutableListOf(
"queen",
"hospital",
"basketball",
"cat",
"change",
"snail",
"soup",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll",
"bubble"
)
wordList.shuffle()
}
/**
* Moves to the next word in the list
*/
private fun nextWord() {
//Select and remove a word from the list
if (wordList.isEmpty()) {
//gameFinished()
} else {
word = wordList.removeAt(0)
}
}
/** 버튼 클릭에 대한 데이터 처리 **/
fun onSkip() {
score--
nextWord()
}
fun onCorrect() {
score++
nextWord()
}
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
}
*ViewModel 사용시 주의점
ViewModel 사용시 activity, fragment, view에 대한 컨택스트를 저장해서는 안된다. configuration change 뷰가 재생성될때 viewmodel은 그 수명주기가 다르기 때문에 메모리 릭을 발생시킬 수 있다. viewmodel에서 context가 필요한 경우는 application 컨택스트를 저장해야하며 이런 용도로 AndroidViewModel 클래스를 제공한다.
-------------------------------------추가 수정(20.6.15)----------------------------------------------
*ViewModelProvider로 viewmodel을 생성하는 이유
viewmodel은 그 목적상 activity/fragment와 생명주기를 달리 해야한다.
그렇기 때문에 new로 생성해주는 것이 아니라 provider의 개념으로 생성해준것이다.
(내부 코드를 들어가보면 ViewModelStore 라는 곳에 hashmap 형태로 저장되어 여기서 viewmodel을 등록하고 가져올 수 있는 형태이다.)
*Viewmodel의 또 다른 장점 : fragment강 data 공유
viewmodel의 장점으로는 configuration change에 대응할 수 있다는 점을 꼽았다. 구글측에서 viewmodel 클래스를 더욱 잘 활용할 수 있도록 여러 기능들을 추가해주고 있는데 그 중 하나가 "Fragment간 data 공유"이다.
fragment는 activity 위에 만들어지는데 activity의 viewmodel을 공유함으로써 fragment간 데이터 공유가 가능하게 된다.
(구체적인 사용 방법은 아래 글을 참고)
developer.android.com/topic/libraries/architecture/viewmodel#java
*여기서 주의사항은 fragment에서 viewmodel을 만들때 Activity의 viewmodel이기 때문에 requireActivity()를 넣어주어 activity의 viewmodelstore를 전달해야한다.
*참고 자료
'모던 안드로이드 > Udacity Android with kotlin' 카테고리의 다른 글
[Udacity android with kotlin] 5.Architectue - LiveData (0) | 2020.06.15 |
---|---|
[advanced android] android에서 Test란? TDD란? #Test와 AAC (1) | 2020.04.21 |
Retrofit+liveData+moshi 로 네트워크 통신하기 (0) | 2020.04.19 |
(ViewModel+LiveData)Bottom Navigation 수직/수평 전환시 fragment 초기화 문제 해결 #configuration change (0) | 2020.04.16 |
(AAC 응용) LiveData+Room+RecyclerView #DiffUtil (0) | 2020.04.15 |