android
package와 androidx
packageActivity와 Fragment의 기본 틀을 미리 짜두고 싶어서 추상 클래스를 만들었다.
import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
abstract class BaseActivity<B : ViewBinding>(
private val inflate: (LayoutInflater) -> B
) : AppCompatActivity() {
protected lateinit var binding: B
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = inflate(layoutInflater)
setContentView(binding.root)
}
}
viewBinding 부분을 제외하면 Fragment도 같은 구조를 사용하였기에 생략한다. 그리고 이 베이스 코드를 활용한 Activity 코드는 다음과 같다.
import android.os.Bundle
import hs.project.cof.base.BaseActivity
import hs.project.cof.databinding.ActivityMainBinding
class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::inflate) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
}
}
그런데 다음 오류를 발견했다.
Type argument is not within its bounds.
Expected:
ViewBinding
Found:
ActivityMainBinding
분명 ViewBinding을 상속 받도록 코딩했는데 무슨 문제일까? 삽질 끝에 찾아보니 import한 ViewBinding 라이브러리가 어느 package에 속해있는지, 그리고dataBinding의 사용 여부에 따라 해당 오류가 생기는 거였다.
MainActivity 레이아웃 파일의 최상위 레이아웃이 <layout>
이었다. dataBinding을 활용하려 했기 때문인데 그래서 이 경우에는 androidx
패키지의 ViewBinding을 사용해야 한다.
import androidx.viewbinding.ViewBinding
왜냐하면 android
패키지는 기존의 안드로이드 프레임워크에서 제공하는 클래스와 API를 포함하고 있고, androidx
패키지는 Jetpack 라이브러리와 함께 제공되기 때문이다.
간단하기 때문에 코드로 살펴보자.
자식 dialogFragment
class SettingFragment : DialogFragment() {
private val viewModel: ChatViewModel by activityViewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
builder.setTitle("버전을 선택해주세요")
.setItems(
R.array.version_array,
DialogInterface.OnClickListener { dialog, which ->
val versions = resources.getStringArray(R.array.models_array)
val selectedValue = versions[which]
viewModel.setModel(selectedValue)
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
setTitle
: 타이틀setItems
: 출력할 아이템resources.getIntArray(R.array.color_values)
는 string 배열 리소스를 가져와서 versions
배열에 저장한다. 그런 다음 versions[which]
는 선택한 인덱스 which
에 해당하는 string 값을 가져온다. 이 값을 selectedValue
변수에 할당하고, viewModel에 세팅한다.
dialogFragment를 호출하는 부모 Fragment
class ChatFragment : BaseFragment<FragmentChatBinding>(FragmentChatBinding::inflate) {
..
private fun modelSettingListener() {
binding.mainActionbarSettingIb.setOnClickListener {
val dialogFragment = SettingFragment()
dialogFragment.show(childFragmentManager, "settings_dialog")
}
}
}
데이터를 가지고 있는 ViewModel
class ChatViewModel : ViewModel() {
..
private var _model = String()
fun setModel(model: String) {
_model = model
}
..
fun getMessage(msg: String) {
viewModelScope.launch {
_apiStatus.value = MessageApiStatus.LOADING
try {
val chat = ChatRequest(model = _model, messages = listOf(RequestMessage(content = msg, role = "user")))
addMessage(Message(ChattingApi.retrofitService.getMessage(BuildConfig.API_KEY, chat).choices[0].message.content, SEND_BY_BOT))
_apiStatus.value = MessageApiStatus.DONE
} catch (e: Exception) {
addMessage(Message("다음 사유로 응답에 실패했습니다.\n$e", SEND_BY_BOT))
_apiStatus.value = MessageApiStatus.ERROR
}
}
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="version_array">
<item>채팅</item>
<item>번역</item>
<item>질문</item>
</string-array>
<string-array name="models_array">
<item>gpt-3.5-turbo</item>
<item>gpt-3.5-turbo</item>
<item>gpt-3.5-turbo</item>
</string-array>
</resources>