김호쭈
DevForYou
김호쭈
전체 방문자
오늘
어제
  • 분류 전체보기 (321)
    • • 데이터베이스(DB) (9)
      • __SQL__ (9)
    • •알고리즘(Algorithm ) (117)
      • 문제풀이 (99)
      • 스터디 (14)
      • 알고리즘 팁 (4)
    • •Compter Science (57)
      • Operating System (25)
      • Computer Network (1)
      • Computer Vision (16)
      • Artificial Intelligence (14)
      • Software Technology (1)
    • • 독서 (36)
      • Design Pattern (24)
      • 객체지향의 사실과 오해 (1)
      • Object Oriented Software En.. (11)
    • • 개발 (26)
      • React (3)
      • node.js (6)
      • Django (11)
      • Spring boot (6)
    • • 개발Tip (4)
      • GitHub (0)
    • •프로젝트 (2)
      • 물물 (2)
    • •App (54)
      • 안드로이드 with Kotlin (50)
      • 코틀린(Kotiln) (4)
    • •회고 (8)
    • •취준일기 (3)
    • • 기타 (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Remote저장소
  • ㄱ
  • 깃허브데스크탑
  • local저장소
  • 원격저장소
  • 로컬저장소
  • GitHubDesktop
  • KMU_WINK

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
김호쭈

DevForYou

[안드로이드 코드랩/Room-3] 네비게이션을 통한 프래그먼트 전환시 인자 넘겨주기, 라이브데이터를 이용한 스낵바 구현, 뷰모델 및 라이브데이터 활용하기
•App/안드로이드 with Kotlin

[안드로이드 코드랩/Room-3] 네비게이션을 통한 프래그먼트 전환시 인자 넘겨주기, 라이브데이터를 이용한 스낵바 구현, 뷰모델 및 라이브데이터 활용하기

2022. 8. 30. 02:20

<이전글>

 

[안드로이드 코드랩] 코루틴을 이용해 Room CRUD구현해보기, LiveData를 반환하는 DAO

<이전글> Android Kotlin 기초  | 학습 과정  | Android Developers Android Kotlin 기초 Android Kotlin 기초 교육 과정은 Google Developers 교육팀에서 만들었습니다. 이.." data-og-host="devforyou.tistory..

devforyou.tistory.com

<코드랩>

 

Android Kotlin 기초  |  학습 과정  |  Android Developers

Android Kotlin 기초 Android Kotlin 기초 교육 과정은 Google Developers 교육팀에서 만들었습니다. 이 교육 과정에서는 Android Kotlin 프로그래밍 개념에 관해 알아보고 다양한 앱을 빌드합니다. Android Kotlin 기

developer.android.com


 

# 시작하며

 이번 코드랩에서는 평점기능 추가를 위한 프래그먼트 연결, 버튼 상태관리, 스낵바띄우기를 배웠다. LiveData를 활용해서 비슷한 방법을 사용해서 기능을 구현할 수 있었다.

 이번 코드랩에서는 이상하게 에러가 너무 많이생겨서 에러를 해결하는데 시간을 많이 잡아먹었다.

 

# 평점 기능 연결하기1

START버튼 -> STOP을 누르면 수면 평점을 클릭하여 저장하는 기능을 구현해야 한다. 

먼저 START를 누르면 만들어지는 tonight에 대해서 STOP을 누를시 그 객체 자체를 넘겨줘야한다. 그 객체에서 nightId값을 통해서 디비에 선택한 수면퀄리티를 update해야하기 때문이다. 

SleepTrackerViewModel에 아래를 추가해주자.

private val _navigateToSleepQuality = MutableLiveData<SleepNight>()

val navigateToSleepQuality: LiveData<SleepNight>
   get() = _navigateToSleepQuality
fun doneNavigating() {
   _navigateToSleepQuality.value = null
}

 

fun onStopTracking() {
    viewModelScope.launch {
        val oldNight = tonight.value ?: return@launch
        oldNight.endTimeMilli = System.currentTimeMillis()
        update(oldNight)
        
        //추가
        _navigateToSleepQuality.value = oldNight
    }
}

이렇게 함으로써, STOP버튼을 클릭시, _navigateToSleepQuality라이브데이터가 변화됨을 UI컨트롤러에서 옵저빙하다가 _navigateToSleepQuality가 바뀜을 알고 퀄리티프래그먼트를 연결시켜준다. 여기서 night.nightID를 인자로 넘겨주어 어떤 에티티에 update해야하는지 가지 알려준다.

SleepTrackerFragement에 아래를 추가해주자.

sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer { night ->
    night?.let {
        this.findNavController().navigate(
            SleepTrackerFragmentDirections
                .actionSleepTrackerFragmentToSleepQualityFragment(night.nightId))
        Log.d("mydebug", "--@@--")
        sleepTrackerViewModel.doneNavigating()
    }
})

this와 SleepTrackerFragmentDirections에 빨간줄이 뜨지만 실행은 잘 된다. 이거때문에 꽤 시간을 잡아먹었는데 결국 빨간줄은 없애지는 못하고, 빌드는 잘 됨을 확인 했다.

sleepTrackerViewModel.doneNavigating()을 호출해줌으로써 _navigateToSleepQuality을 null로 만들어주어 재호출되지 않도록 한다.

 

# 평점 기능 연결하기2 - 저장

이제 넘겨진 퀄리티프래그먼트에서 평점을 누르면 DB에 값이 update되고, 홈화면으로 넘어오도록 해줘야한다. SleepQualityViewModel을 다음과 같이 만들어 줘야 한다. 뷰모델은 팩토리로 만들어지는 것에 유념하자.

class SleepQualityViewModel(
    private val sleepNightKey: Long = 0L,
    val database: SleepDatabaseDao
) : ViewModel() {
    private val _navigateToSleepTracker = MutableLiveData<Boolean?>()

    val navigateToSleepTracker : LiveData<Boolean?>
        get() = _navigateToSleepTracker

    fun doneNavigating() {
        _navigateToSleepTracker.value = null
    }

    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
        }
    }
}

여기서 _navigateToSleepTracker을 통해서 평점이 클릭됨을 옵저버를 통해 감지하고 홈 프래그먼트로 전달시키는 역할을 한다. 

onSetSleepQuality은 onClick리스너로 묶어서 평점이 클릭되면 실행되는 함수이다. 즉 평점 클릭 -> onSetSleepQuality호출 ( db에 값 반영) -> _navigateToSleepTracker을 통해 홈프래그먼트로 넘겨주기의 작업으로 진행된다.

뷰모델생성을 위한 뷰모델 팩토리를 아래와 같이 만들어준다.

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")
   }
}

사실 잘 이해는 가지 않지만 보일러플레이트형식이라고 코드랩에서 소개한다. 최근 GOF를 공부하기위해서 책을 열심히 읽고 있는 중이다. 언젠가는 저런식으로 만드는 이유를 이해할 수 있을 것이다.

이제 뷰모델을 생성하는 과정을 거친 후, 위에서 말한 옵저버를 연결하여 홈화면으로 넘어가기 위해서 QualityFragement를 만들어주자.

class SleepQualityFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
                              
        // Get a reference to the binding object and inflate the fragment views.
        val binding: FragmentSleepQualityBinding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_sleep_quality, container, false)

        val application = requireNotNull(this.activity).application
        val arguments = SleepQualityFragmentArgs.fromBundle(requireArguments())
        val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
        val viewModelFactory = SleepQualityViewModelFactory(arguments.sleepNightKey, dataSource)

        val sleepQualityViewModel = ViewModelProvider(this,viewModelFactory).get(SleepQualityViewModel::class.java)
        binding.sleepQualityViewModel = sleepQualityViewModel

        sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer {
            if (it == true) { // Observed state is true.
                this.findNavController().navigate(
                    SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())
                Log.d("mydebug","--@@--")
                sleepQualityViewModel.doneNavigating()
            }
        })
        
        return binding.root
    }
}

여기도 SleepQualityFragmentArgs,SleepQualityFragmentDirections 에도 빨간 에러가 뜨지만 앱빌드는 성공적으로 이루어진다. 왜 그런지 모르겠다.. 

xml의 각 이미지에 databindng을 통해 onClick리스너를 연결해주도록 하자.

android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(5)}"

위와 같이 0~5까지 하나씩 입력해준다.

 

# 버튼 활성/비활성

SleepTrackerViewModel에 버튼 활성/비활성을 위한 변수를 만들어 준 후, xml에 데이터바인딩을 통해 연결해주자.

val startButtonVisible = Transformations.map(tonight) {
    it == null
}
val stopButtonVisible = Transformations.map(tonight) {
    it != null
}
val clearButtonVisible = Transformations.map(nights) {
    it?.isNotEmpty()
}

버튼의 enabled속성은 true/false값을 사용한다.

android:enabled="@{sleepTrackerViewModel.startButtonVisible}"

android:enabled="@{sleepTrackerViewModel.stopButtonVisible}"

android:enabled="@{sleepTrackerViewModel.clearButtonVisible}"

 

# 스낵바 만들기

토스트 메시지와 비슷한 스낵바를 만들어 주도록 한다. 스낵바가 나오는 타이밍 또한 LiveData를 통해서 관리할 수 있다. 맨 처음 했던거와 비슷한 방식이다. onClear가 눌리면 스낵바가 나오도록 해주자.

onClear 버튼이 눌리면 만들어준 라이브데이터 변수가 true로 바뀌고, UI컨트롤러에서 옵저빙해서 스낵바를 띄우도록 한다.

SleepTrackerViewModel에 아래와 같이 추가해주자.

// 상태 관리를 위한 라이브데이터
private var _showSnackbarEvent = MutableLiveData<Boolean>()

val showSnackBarEvent: LiveData<Boolean>
   get() = _showSnackbarEvent
fun doneShowingSnackbar() {
   _showSnackbarEvent.value = false
}
// CLEAR버튼 클릭시 호출되는 메서드
fun onClear() {
    viewModelScope.launch {
        clear()
        tonight.value = null
        
        // 추가
        _showSnackbarEvent.value = true
    }
}

이제 UI컨트롤러(프래그먼트)에서 옵저빙을 통해 스낵바를 띄우기만 하면 된다.

sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer {
    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()
    }
})

 

# 마치며

이번 코드랩을 통해서 ROOM-DB사용법, 코루틴을 통한 DB 호출, 프래그먼트 전환시 인자 넘겨주기, 스낵바, 라이브데이터 다루기 등을 학습할 수 있었다. 깊은 이해는 아니지만, 아키텍처와 ROOM을 결합하여 이러한 플로우로 사용됨을 알 수 있었다. 다음 시간에는 안드로이드의 꽃이라고 불릴 수 있는 리사이클러뷰에 대해서 공부할 것 같다. 지금까지 리사이클러뷰를 많이 써오긴 했지만, 구글에서 제시하는 방법은 어떤 것일지 궁금하기도 하다.

저작자표시 (새창열림)

'•App > 안드로이드 with Kotlin' 카테고리의 다른 글

[안드로이드 코드랩/리사이클러뷰-1] 리사이클러 뷰 개념, 뷰 홀더 개념, 리사이클러뷰 만들기  (0) 2022.09.01
[안드로이드 코드랩/Room-2] 코루틴을 이용해 Room CRUD구현해보기, LiveData를 반환하는 DAO  (0) 2022.08.21
[안드로이드 코드랩/Room-1] 안드로이드 Room-DB만들기, Entity개념, DAO  (1) 2022.08.21
[안드로이드 코드랩/아키텍처-4] LiveData를 transformations사용하여 변환하기, 안드로이드 타이머 구현하기  (0) 2022.08.20
[안드로이드 코드랩/아키텍처-3] 데이터바인딩(data-binding) 을통한 뷰모델(viewModel), 라이브데이터(liveData) 결합  (0) 2022.08.19
    '•App/안드로이드 with Kotlin' 카테고리의 다른 글
    • [안드로이드 코드랩/리사이클러뷰-1] 리사이클러 뷰 개념, 뷰 홀더 개념, 리사이클러뷰 만들기
    • [안드로이드 코드랩/Room-2] 코루틴을 이용해 Room CRUD구현해보기, LiveData를 반환하는 DAO
    • [안드로이드 코드랩/Room-1] 안드로이드 Room-DB만들기, Entity개념, DAO
    • [안드로이드 코드랩/아키텍처-4] LiveData를 transformations사용하여 변환하기, 안드로이드 타이머 구현하기
    김호쭈
    김호쭈
    공부하고 정리하고 기록하기

    티스토리툴바