Android Kotlin Fundamentals는 codelab에 올라와있는 강의를 한글로 번역한 내용입니다.
Codelabs for Android Kotlin Fundamentals | Training Courses
Content and code samples on this page are subject to the licenses described in the Content License. Java is a registered trademark of Oracle and/or its affiliates. Last updated 2020-09-14 UTC.
developer.android.com
코드는 github를 참고해주세요.
06-3. Use LiveData To Control Button State
1. Add navigation
Step 1: Inspect the code
1) SleepQualityFragment를 살펴보면 layout을 inflate 시키고 application을 가져오고 binding.root를 리턴한다
2) navigation.xml의 디자인 에디터를 열면 SleepTrackerFragment에서 SleepQualityFragment로의 navigation path와 반대로 SleepQualityFragment에서 SleepTrackerFragment로 이동하는 navigation path를 볼 수 있다
3) navigation.xml 코드를 열어 sleepNightKey라는 이름을 가진 <argument>를 살펴본다. 사용자가 SleepTrackerFragment에서 SleepQualityFragment로 이동할 때 app은 night를 업데이트 하기 위해 sleepNightKey 데이터를 SleepQualityFragment로 전달한다.
Step 2: Add navigation for sleep-quality tracking
navigation graph는 이미 SleepTrackerFragment에서 SleepQualityFragment로 이동, 그리고 그 반대의 경우도 포함하고 있다. 그러나 한 프래그먼트에서 다른 프래그먼트로 이동하는 click handler는 구현되어 있지 않다. 이제 코드에 ViewModel을 추가해보자 click handler에서 앱이 다른 destination으로 이동할 때 변경되는 LiveData를 설정한다. 프래그먼트는 LiveData를 관찰하고 데이터가 변경되면 프래그먼트가 destination으로 이동하여 viewModel에게 완료되었음을 알리고 state variable을 재설정한다.
1) SleepTrackerViewModel을 열어서 사용자가 stop button을 탭하면 수면 품질 등급을 수집하는 SleepQualityFragment로 이동하도록 navigation을 추가해야한다
2) SleepTrackerViewModel에서 앱이 SleepQualityFragment로 이동할 때 변경되는 LiveData를 만든다. 캡슐화를 사용하면 단지 gettable만 할 수 있는 LiveData를 만들어 viewModel에 전달할 수 있다. 이 코드를 클래스 본문의 최상위에 둔다
private val _navigateToSleepQuality = MutableLiveData<SleepNight>()
val navigateToSleepQuality: LiveData<SleepNight>
get() = _navigateToSleepQuality
3) navigation을 trigger시키는 변수를 reset하는 doneNavigation() 함수를 추가한다
fun doneNavigating() {
_navigateToSleepQuality.value = null
}
4) onStopTracking()을 호출하는 stop button을 클릭 시 SleepQualityFragment로 이동하도록 트리거한다. launch{} 블록의 마지막에서 _navigateToSleepQuality 변수에 night 값을 넣는다. 변수가 값을 가지고 있으면 app이 SleepQualityFragment로 이동할 때 night를 함께 넘긴다.
_navigateToSleepQuality.value = oldNight
5) SleepTrackerFragment는 앱이 언제 navigate 할지 알기 위해 _navigateToSleepQuality를 observe 한다. SleepTrackerFragment의 onCreateView()에 navigateToSleepQuality()에 대한 observer를 추가한다. androidx.lifecycle.Observer를 import한다
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer {
})
6) observer 블럭 안에서 현재 night의 id를 전달하고 이동시킨다. 그런 다음 doneNavigation()을 호출한다.
night ->
night?.let {
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepQualityFragment(night.nightId))
sleepTrackerViewModel.doneNavigating()
}
7) 앱을 실행시키고 Start 버튼을 누른 다음 Stop 버튼을 눌러서 SleepQualityFragment로 이동하는지 확인한다.
2. Record the sleep quality
SleepQualityFragment를 업데이트 하기 위해 ViewModel과 ViewModelFactory를 만들어야 한다.
Step 1: Create a ViewModel and a ViewModelFactory
1) sleepquality 패키지에서 SleepQualityViewModel.kt를 연다
2) sleepNightKey와 database를 인자로 가지는 SleepQualityViewModel 클래스를 생성하고 SleepTrackerViewModel에서 했던 것처럼 factory로 database를 전달한다. 또한 navigation에서 sleepNightKey를 전달해야한다.
class SleepQualityViewModel(
private val sleepNightKey: Long = 0L,
val database: SleepDatabaseDao) : ViewModel() {
}
3) SleepTrackFragment로 다시 navigate 하기 위해 위에서 사용했던 패턴과 똑같이 _navigateToSleepTracker를 선언하고 navigateToSleepTracker과 doneNavigating() 함수를 구현한다.
private val _navigateToSleepTracker = MutableLiveData<Boolean?>()
val navigateToSleepTracker: LiveData<Boolean?>
get() = _navigateToSleepTracker
fun doneNavigating() {
_navigateToSleepTracker.value = null
}
4) ClickHandler인 onSetSleepQuality()를 만든다. 06-2에서 진행했던 코루틴 패턴을 이용한다
- viewModelScope 범위에서 코루틴을 실행한다
- sleepNightKey를 이용하여 night를 db에서 가져온다
- sleep quality를 설정한다
- 데이터베이스에 업데이트한다
- navigation을 트리거한다.
fun onSetSleepQuality(quality: Int) {
viewModelScope.launch {
val tonight = database.get(sleepNightKey) ?: return@launch
tonight.sleepQuality = quality
database.update(tonight)
// Setting this state variable to true will alert the observer and trigger navigation.
_navigateToSleepTracker.value = true
}
}
5) sleepquality 패키지에서 SleepQualityViewModelFactory.kt를 열어 SleepQualityViewModelFactory 클래스를 추가한다. 이 클래스는 이전에 본 것과 동일한 코드를 사용한다.
class SleepQualityViewModelFactory(
private val sleepNightKey: Long,
private val dataSource: SleepDatabaseDao) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepQualityViewModel::class.java)) {
return SleepQualityViewModel(sleepNightKey, dataSource) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
Step 2: Update the SleepQualityFragment
1) SleepQualityFragment.kt를 열어서 onCreateView()에 navigation에서 전달한 argument를 가져와야한다. 이 argument는 SleepQualityFragmentArgs에 있고 bundle로부터 추출해야한다.
val arguments = SleepQualityFragmentArgs.fromBundle(requireArguments())
2) dataSource 값을 얻는다.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
3) dataSource와 SleepNightKey를 인자를 갖는 viewModelFactory를 만든다.
val viewModelFactory = SleepQualityViewModelFactory(arguments.sleepNightKey, dataSource)
4) ViewModel 레퍼런스를 얻는다
val sleepQualityViewModel =
ViewModelProvider(
this, viewModelFactory).get(SleepQualityViewModel::class.java)
5) binding 객체에 viewModel을 추가한다
binding.sleepQualityViewModel = sleepQualityViewModel
6) observer를 추가한다.
sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer {
if (it == true) { // Observed state is true.
this.findNavController().navigate(
SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())
sleepQualityViewModel.doneNavigating()
}
})
Step 3: Update the layout file and run the app
1) fragment_sleep_quality.xml 레이아웃 파일을 열고 <data> 블럭 안에 SleepQualityViewModel 변수를 추가한다
<data>
<variable
name="sleepQualityViewModel"
type="com.example.android.trackmysleepquality.sleepquality.SleepQualityViewModel" />
</data>
2) 6개의 sleep-quallity 이미지 각각에 아래와 같은 클릭 핸들러를 추가한다. 이미지에 quality 등급을 매칭시킨다.
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(5)}"
3. Control button visibility and add a snackbar
이번 단계에서는 사용자가 옳은 선택만 할 수 있도록 transformation map을 사용하여 버튼의 visibility를 관리한다
이제 앱은 잘 작동한다. 사용자가 원하는 만큼 시작 및 중지를 탭할 수 있다. 사용자가 Stop을 탭하면 sleep quality를 입력할 수 있고 사용자가 Clear를 탭하면 백그라운드에 있는 모든 데이터가 자동으로 지워진다. 그러나 모든 버튼은 언제나 활성화 되어있고 클릭 가능한 상태로 남아있어 잘못 누를 가능성이 있다. 이번 단계에서는 사용자가 올바른 선택을 할 수 있도록 TransformationMap을 사용하여 버튼의 visibility를 관리해본다.
Step 1: Update button states
처음에는 start 버튼만 클릭할 수 있도록 enable 시킨다. start 버튼을 누른 뒤에는 stop 버튼을 enable 시키고 start 버튼은 비활성화 한다. clear 버튼은 데이터베이스에 데이터가 있을 때만 활성화 되도록 한다
1) fragment_sleep_tracker.xml 레이아웃 파일을 열어서 각 버튼에 android:enabled 속성을 추가한다. android:enabled 속성은 버튼이 활성화 상태인지 아닌지 나타내는 boolean 값이다.
start_button
android:enabled="@{sleepTrackerViewModel.startButtonVisible}"
stop_button
android:enabled="@{sleepTrackerViewModel.stopButtonVisible}"
clear_button
android:enabled="@{sleepTrackerViewModel.clearButtonVisible}"
2) SleepTrackerViewModel을 열고 1)과 일치하는 변수를 추가한다. 각 변수에 아래와 같이 transformation.map을 할당한다.
- start 버튼은 tonight이 null일 때 enable 상태이다.
- stop 버튼은 tonight이 null이 아닐 때 enable 상태이다
- clear 버튼은 nights가 존재할 때, 즉 데이터베이스에 데이터가 있을 때 enable 상태이어야 한다.
val startButtonVisible = Transformations.map(tonight) {
it == null
}
val stopButtonVisible = Transformations.map(tonight) {
it != null
}
val clearButtonVisible = Transformations.map(nights) {
it?.isNotEmpty()
}
enabled 속성은 attribute 속성과 같지 않다. enabled 속성은 View의 visible 여부가 아니라 View의 활성화 여부만 결정한다. enable의 의미는 sub class마다 다르다. EditText에서의 enable은 사용자가 text를 편집할 수 있지만 disabled에서는 편집할 수 없다. 또한 enable 상태인 Button은 탭 할 수 있지만 disable 버튼은 탭 할 수 없다.
Step 2: Use a snackbar to notify the user
사용자가 데이터베이스에서 데이터를 지우면 스낵바에 확인 메세지를 띄운다. 스낵바는 화면 하단의 메세지를 통해 작업에 대한 간단한 피드백을 제공한다. 스낵바는 어느정도 시간이 경과하면 사라지거나 스크린의 다른곳을 사용자가 터치하거나 사용자의 스와이프를 통해 사라진다. 스낵바를 표시하는 것은 UI의 작업이며 fragment에서 발생해야 한다. 스낵바를 표시하기로 결정하는 것은 ViewModel에서 발생한다. 데이터가 지워질 때 스낵바를 설정하고 트리거하려면 네비게이션 트리거링과 같은 기술을 사용하면 된다.
1) SleepTrackerViewModel에서 캡슐화한 event 변수를 만든다
private var _showSnackbarEvent = MutableLiveData<Boolean>()
val showSnackBarEvent: LiveData<Boolean>
get() = _showSnackbarEvent
2) doneShowingSnackbar() 함수를 구현한다
fun doneShowingSnackbar() {
_showSnackbarEvent.value = false
}
3) SleepTrackerFragment의 onCreateView()에 observer를 추가한다
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer { })
4) observer 블럭 내에서 snackbar를 표시하고 event를 즉시 reset한다
if (it == true) { // Observed state is true.
Snackbar.make(
requireActivity().findViewById(android.R.id.content),
getString(R.string.cleared_message),
Snackbar.LENGTH_SHORT // How long to display the message.
).show()
sleepTrackerViewModel.doneShowingSnackbar()
}
5) SleepTrackerViewModel의 onClear() 메소드에서 event를 트리거 시키기 위해 event의 value값을 launch 블럭 안에서 true로 변경한다
fun onClear() {
viewModelScope.launch {
clear()
tonight.value = null
_showSnackbarEvent.value = true
}
}
'android > android kotlin fundamentals' 카테고리의 다른 글
07-2. DiffUtil and data binding with RecyclerView (1) | 2021.07.02 |
---|---|
07-1. RecyclerView (0) | 2021.06.28 |
06-2. Coroutine and Room (0) | 2021.06.25 |
06-1. Room Database (0) | 2021.02.21 |
05-4. LiveData Transformations (0) | 2021.02.21 |
댓글