TIL) 230328

Hanseul Lee·2023년 3월 29일
0

TIL

목록 보기
15/23

Null safety를 고려해 ViewModel의 변수를 짜보자

class ChatViewModel : ViewModel() {
		private var _messageList = MutableLiveData<MutableList<Message>>()
		val messageList: LiveData<MutableList<Message>> = _messageList

		fun removeLastMessage() {
        _messageList.value!!.removeAt(_messageList.value!!.size-1)
    }
}

null이 될 수 있는 경우, !! 연산자 대신에 안전 호출(?.) 연산자를 사용하는 것이 좋다. 더불어 다음과 같이 예외처리를 해두면 더 좋다.

class ChatViewModel : ViewModel() {
		private var _messageList = MutableLiveData<MutableList<Message>>()
		val messageList: LiveData<MutableList<Message>> = _messageList

		fun removeLastMessage() {
        _messageList.value?.removeAt(_messageList.value?.size?.minus(1) ?: return)
    }
}

viewModel에서 업데이트하는 방식이 잘못되었을 때

채팅을 하는 화면에서 메시지를 입력해도 화면에 출력되지 않는 오류가 있었다.

package hs.project.cof.presentation.viewModel

import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import hs.project.cof.data.remote.model.Messag

class ChatViewModel : ViewModel() {

    private var _messageList = MutableLiveData<MutableList<Message>>()
    val messageList: LiveData<MutableList<Message>> = _messageList

    init {
        clearMessageList()
    }

    fun addMessage(msg: Message) {
        _messageList.value?.add(msg) ?: return
    }

    fun removeLastMessage() {
        _messageList.value?.removeAt(_messageList.value?.size?.minus(1) ?: return)
    }

    fun clearMessageList() {
        _messageList.value = ArrayList<Message>()
    }

}

_messageList의 값을 업데이트하는 방식이 잘못되어 있기 때문이다. _messageList.value?.add(msg)는 Nullable인_messageList.value가 null이면 null을 반환한다. 이 경우에는 null이 반환될 때 아무 일도 일어나지 않게 되어 있기 때문에 새로운 메시지가 추가되지 않았던 것이다. 대신 let 함수를 사용하여 Nullable한 경우에는 아무 일도 하지 않도록 처리하도록 수정하면 다음 동영상과 같이 의도대로 동작한다.

package hs.project.cof.presentation.viewModel

import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import hs.project.cof.data.remote.model.Message

class ChatViewModel : ViewModel() {

    private var _messageList = MutableLiveData<List<Message>>()
    val messageList: LiveData<List<Message>> = _messageList

    init {
        clearMessageList()
    }

    fun addMessage(msg: Message) {
        _messageList.value = _messageList.value?.toMutableList()?.apply { add(msg) }
    }

    fun removeLastMessage() {
        _messageList.value = _messageList.value?.toMutableList()?.apply { removeLastOrNull() }
    }

    fun clearMessageList() {
        _messageList.value = ArrayList<Message>()
    }

}

아래 코드도 좋다.

		fun addMessage(msg: Message) {
        val list = _messageList.value ?: mutableListOf()
        list.add(msg)
        _messageList.value = list
    }

    fun removeLastMessage() {
        val list = _messageList.value ?: return
        list.removeLastOrNull()
        _messageList.value = list
    }

?: 엘비스 연산자는 다음 기록을 참고해 복습하자!

TIL) 0905

viewModel에서 MutableLiveData를 사용하는 이유

ViewModel에서 MutableLiveData를 사용할 때는 일반적으로 MutableLiveData를 가지고 있는 LiveData 객체를 사용해 데이터를 관리한다. LiveData는 수정이 불가능한 읽기 전용 데이터를 제공하므로, 일반적으로 List 대신 MutableList를 사용할 필요가 없다.

따라서 ViewModel에서 MutableLiveData를 사용할 때는 List 대신 불변성을 보장하는 List 인터페이스를 구현하는 객체를 사용하는 것이 더 안전하고 권장된다. 불변성을 보장하는 List는 객체가 생성된 후에 수정할 수 없기 때문에 앱의 안정성과 예측 가능성을 높일 수 있다. 더불어 List를 사용하면 LiveData의 데이터를 더욱 쉽게 공유하고 재사용할 수 있습니다.

그래서 ViewModel에서 MutableLiveData를 사용할 때는 일반적으로 List를 사용하는 것이 좋다. 하지만 ViewModel 내부에서 데이터를 수정해야 할 경우에는 MutableList를 사용할 수 있다는 점도 알아두자. 그래서 다음과 같은 코드를 짠다.

class ChatViewModel : ViewModel() {
    private var _messageList = MutableLiveData<MutableList<Message>>()
    val messageList: LiveData<MutableList<Message>> = _messageList
}

local.properties로 안전하게 API key 관리하기

이 문서는 API key값과 같이 중요한 정보를 하드코딩하지 않고 local.properties를 이용하여 관리하는 내용을 정리했다.

  1. local.properties에 앱 키를 등록한다.

    ## This file must *NOT* be checked into Version Control Systems,
    # as it contains information specific to your local configuration.
    #
    # Location of the SDK. This is only used by Gradle.
    # For customization when using a Version Control System, please read the
    # header note.
    #Wed Aug 17 00:12:05 KST 2022
    
    sdk.dir=C\:\\Users\\hj-user3\\AppData\\Local\\Android\\Sdk
    kakao_app_key={app_key}
  2. build.gradle (:app)

    local.properties에 접근해서 데이터를 가져오는 코드를 android 상단에 입력한다.

    Properties properties = new Properties()
    // build.gradle과 같은 폴더에 위치한 local 파일
    //properties.load(project.file('local.properties').newDataInputStream())
    
    // root에 위치한 local 파일
    properties.load(project.rootProject.file('local.properties').newDataInputStream())
  3. buildTypes 혹은 defaultConfig 등에 다음 코드를 입력한다.

    buildConfigField "String", "BUILD_TIME", properties['kakao_app_key']

    공식 문서에 따르면 다음과 같이 활용 가능하다. Manifest에 등록할 때는 resValue를 이용한다.

    android {
      ...
      buildTypes {
        release {
          // These values are defined only for the release build, which
          // is typically used for full builds and continuous builds.
          buildConfigField("String", "BUILD_TIME", "\"${minutesSinceEpoch}\"")
          resValue("string", "build_time", "${minutesSinceEpoch}")
          ...
        }
        debug {
          // Use static values for incremental builds to ensure that
          // resource files and BuildConfig aren't rebuilt with each run.
          // If these rebuild dynamically, they can interfere with
          // Apply Changes as well as Gradle UP-TO-DATE checks.
          buildConfigField("String", "BUILD_TIME", "\"0\"")
          resValue("string", "build_time", "0")
        }
      }
    }
    ...
  4. Manifest에서 다음과 같이 활용할 수 있다.

    // local.properties
    kakao_oRUauth_host="test"
    // build.gradle(:app)
    defaultConfig {
            applicationId "hs.project.secondweek"
            minSdk 21
            targetSdk 32
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    
            buildConfigField "String", "KAKAO_API_KEY", properties["kakao_api_key"]
            resValue "string", "kakao_oauth_host", properties["kakao_oauth_host"]
        }
    // Manifest.xml
    <data android:host="oauth"
                        android:scheme="@string/kakao_oauth_host" />
  5. 코틀린 클래스 코드에서는 다음과 같이 BuildConfig를 선언하여 사용할 수 있다.

    private fun requestMsg(msg: String) {
            val chat = Chat(model = "gpt-3.5-turbo",
                            messages = listOf(RequestMessage(content = msg, role = "user")))
            chattingService(this).requestMessage(BuildConfig.BUILD_TIME, chat)
        }

Android 개발자 문서 “Gradle tips and recipes”

0개의 댓글