막무가내 삽질 블로그
Android ViewModel + LiveData + Data Binding 본문
개발자 문서, 유튜브, 코드랩을 참고했다.
ViewModel ?
AAC ViewModel은 UI관련 데이터를 저장하고 관리하기 위하여 설계된 뷰모델클래스이다.
ViewModel의 LifeCycle은 액티비티가 onCreate되고 onDestroy될 때까지 존재한다.
앱이 회전 할 때와 같이 액티비티가 여러번 호출 될 수 있지만(onCreate) ViewModel은 계속 유지된다.
장점 : 싱글톤 객체처럼 사용가능하다, 프래그먼트 중개자로 액티비티를 사용하지 않아도 된다, 화면 회전 문제
주의사항 : ViewModel 내부에 액티비티,프래그먼트,뷰에 대한 Context를 저장해서는 안된다. (Application Context제외)
LiveData ?
LiveData는 LifeCycle의 Observer다. LiveData는 관찰 가능한 데이터 홀더 클래스이다. 즉, 생명주기를 알고 있는 데이터 타입? 이라고 생각하면 될 것 같다.
LiveData는 옵저버 패턴을 따른다. 데이터의 변경이 일어났을 때 콜백으로 받아 처리 한다. 데이터의 변경이 될때 마다 콜백을 실행하는데 LifeCycle을 알기 때문에 필요하지 않을 땐 콜백이 실행되지 않는다.
장점 : Data와 UI간 동기화, 메모리 누수 방지, 수동적인 생명주기가 필요없음, UI컨트롤러의 상태변경이 쉬움, 자원공유
ViewModel
class UserProfileViewModel : ViewModel() {
// MutableLiveData는 변경할 수 있는 LiveData
private val _user = MutableLiveData<User>()
// LiveData 변경할 수 없음
val user : LiveData<User>
get() = _user
}
Activity
override fun onCreate(saveInstanceState: Bundle?) {
//..
userViewModel.user.observe(this, Observer { user ->
userNameTextView.text = user?.name
})
}
user.setValue(newUser) // UI Thread
user.postValue(newUser) // Background Thread
LiveData는 다른 관측 가능한 객체와 다른 점은 LifeCycle를 인식한다는 것이다. 즉, UI가 화면에 있는지 없는지 여부를 알고 있을 때는 데이터가 변경되고 없으면 데이터가 변경되지 않는다. 왜냐면 LiveData는 UI에 상태에 대해 알고 있기 때문에이다. LiveData를 더 잘쓰려면 데이터 바인딩을 사용해야 한다.
Data Binding ?
데이터를 레이아웃에 바인딩을 해주는 것을 의미한다.
즉, 데이터를 바인딩 하여 View에서 발생하는 이벤트를 ViewModel에 알려 ViewModel은 업데이트한 데이터를 View에게 보여준다.
xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="vm"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/game_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".screens.game.GameFragment">
<TextView
android:id="@+id/word_is_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:fontFamily="sans-serif"
android:text="@string/word_is"
android:textColor="@color/black_text_color"
android:textSize="14sp"
android:textStyle="normal"
app:layout_constraintBottom_toTopOf="@+id/word_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/word_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:fontFamily="sans-serif"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
android:textColor="@color/black_text_color"
android:textSize="34sp"
android:textStyle="normal"
android:text="@{@string/quote_format(vm.word)}"
app:layout_constraintBottom_toTopOf="@+id/score_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/word_is_text"
app:layout_constraintVertical_chainStyle="packed"
tools:text=""Tuna"" />
<TextView
android:id="@+id/timer_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:fontFamily="sans-serif"
android:textColor="@color/grey_text_color"
android:textSize="14sp"
android:textStyle="normal"
android:text="@{vm.curretTimeString}"
app:layout_constraintBottom_toTopOf="@+id/score_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="0:00" />
<TextView
android:id="@+id/score_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:fontFamily="sans-serif"
android:textColor="@color/grey_text_color"
android:textSize="14sp"
android:textStyle="normal"
android:text="@{@string/score_format(vm.score)}"
app:layout_constraintBottom_toTopOf="@+id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="Score: 2" />
<Button
android:id="@+id/skip_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/skip"
android:theme="@style/SkipButton"
android:onClick="@{() -> vm.onSkip()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/correct_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_end="96dp" />
<Button
android:id="@+id/correct_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/got_it"
android:theme="@style/GoButton"
android:onClick="@{() -> vm.onCorrect()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/skip_button"
app:layout_constraintTop_toTopOf="@+id/guideline" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
viewmodel
class GameViewModel : ViewModel() {
private val _word = MutableLiveData<String>()
val word: LiveData<String>
get() = _word
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
get() = _score
fun onSkip() {
/*...*/
}
fun onCorrect() {
/*...*/
}
override fun onCleared() {
super.onCleared()
/*...*/
}
}
fragment
class GameFragment : Fragment() {
private lateinit var viewModel: GameViewModel
private lateinit var binding: GameFragmentBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
viewModel = ViewModelProvider(this).get(GameViewModel::class.java)
binding.vm = viewModel
binding.lifecycleOwner = this
return binding.root
}
}
LiveData, LiveData observers
www.notion.so/imwj/LiveData-LiveData-observers-f8e8f70f75264dc48f3812bd6875ee56
참:https://www.youtube.com/watch?v=OMcDk2_4LSk
참:https://developer.android.com/topic/libraries/architecture/livedata
참:https://developer.android.com/topic/libraries/data-binding
'Android' 카테고리의 다른 글
Room에 저장된 날짜와 현재날짜 비교 (0) | 2020.03.23 |
---|---|
Android Room Database - Kotlin (0) | 2020.03.01 |
Android Jetpack Navigation (0) | 2020.03.01 |
안드로이드 Room 데이터 베이스 (0) | 2020.02.15 |
안드로이드 액티비티 + 프래그먼트 생명주기 상태정리 (0) | 2020.02.10 |