반복적인 액티비티, 프래그먼트 사용을 위한 템플릿을 만들어 사용하면 유용하다.
// 액티비티의 기본을 작성, 뷰 바인딩 활용
abstract class BaseActivity<B : ViewBinding>(private val inflate: (LayoutInflater) -> B) :
AppCompatActivity() {
protected lateinit var binding: B
private set
lateinit var mLoadingDialog: LoadingDialog
// 뷰 바인딩 객체를 받아서 inflate해서 화면을 만들어줌.
// 즉 매번 onCreate에서 setContentView를 하지 않아도 됨.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = inflate(layoutInflater)
setContentView(binding.root)
}
// 로딩 다이얼로그, 즉 로딩창을 띄워줌.
// 네트워크가 시작될 때 사용자가 무작정 기다리게 하지 않기 위해 작성.
fun showLoadingDialog(context: Context) {
mLoadingDialog = LoadingDialog(context)
mLoadingDialog.show()
}
// 띄워 놓은 로딩 다이얼로그를 없앰.
fun dismissLoadingDialog() {
if (mLoadingDialog.isShowing) {
mLoadingDialog.dismiss()
}
}
// 토스트를 쉽게 띄울 수 있게 해줌.
fun showCustomToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
매번 새로 바인딩하지 않고 바인딩 객체를 넘겨주면 layout inflate를 해주도록 하는 기본 템플릿이다.
이를 사용할 떄는
class MainActivity : BaseActivity<ActivityMainBinding>(ActivityMainBinding::inflate) {
..
와 같이 사용할 수 있다.
// Fragment의 기본을 작성, 뷰 바인딩 활용
abstract class BaseFragment<B : ViewBinding>(
private val bind: (View) -> B,
@LayoutRes layoutResId: Int
) : Fragment(layoutResId) {
private var _binding: B? = null
lateinit var mLoadingDialog: LoadingDialog
protected val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = bind(super.onCreateView(inflater, container, savedInstanceState)!!)
return binding.root
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
fun showCustomToast(message: String) {
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
}
fun showLoadingDialog(context: Context) {
mLoadingDialog = LoadingDialog(context)
mLoadingDialog.show()
}
fun dismissLoadingDialog() {
if (mLoadingDialog.isShowing) {
mLoadingDialog.dismiss()
}
}
}
위 예시에서는 로딩중일 때 창을 띄우기 위해 loadingDialog 창을 부르는 함수가 있는데, 이는 따로 구현을 해준 LoadingDialog 클래스이다.
class LoadingDialog(context: Context) : Dialog(context) {
private lateinit var binding: DialogLoadingBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
binding = DialogLoadingBinding.inflate(layoutInflater)
setContentView(binding.root)
setCanceledOnTouchOutside(false)
setCancelable(false)
window!!.setBackgroundDrawable(ColorDrawable())
window!!.setDimAmount(0.2f)
}
override fun show() {
if(!this.isShowing) super.show()
}
}
class SharedPreferencesUtil(context: Context) {
private var preferences: SharedPreferences =
context.getSharedPreferences(ApplicationClass.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
fun addUserCookie(cookies: HashSet<String>) {
val editor = preferences.edit()
editor.putStringSet(ApplicationClass.COOKIES_KEY_NAME, cookies)
editor.apply()
}
fun getUserCookie(): MutableSet<String>? {
return preferences.getStringSet(ApplicationClass.COOKIES_KEY_NAME, HashSet())
}
fun getString(key:String): String? {
return preferences.getString(key, null)
}
}
class SplashActivity : BaseActivity<ActivitySplashBinding>(ActivitySplashBinding::inflate) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Handler(Looper.getMainLooper()).postDelayed({
startActivity(Intent(this, MainActivity::class.java))
finish()
}, 1500)
}
}
class MyListAdapter: ListAdapter<SearchResult, MyListAdapter.CustomViewHolder>(SearchResultComparator){
// var list = mutableListOf<SearchResult>()
companion object SearchResultComparator: DiffUtil.ItemCallback<SearchResult>() {
override fun areItemsTheSame(oldItem: SearchResult, newItem: SearchResult): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: SearchResult, newItem: SearchResult): Boolean {
return oldItem.no == newItem.no
}
}
class CustomViewHolder (val binding: ListItemDatabindingBinding): RecyclerView.ViewHolder(binding.root) {
fun bindInfo(data: SearchResult){
binding.apply{
searchResult = data
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val binding:ListItemDatabindingBinding = ListItemDatabindingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return CustomViewHolder(binding).apply{
binding.root.setOnClickListener{
Toast.makeText(parent.context, "onCreateViewHolder: adapterPosition:${adapterPosition}, layoutPosition: ${layoutPosition}", Toast.LENGTH_SHORT).show()
}
}
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
// Log.d(TAG, "onBindViewHolder: ${list.get(position)}")
// holder.bindInfo(list.get(position))
Log.d(TAG, "onBindViewHolder: ${getItem(position)}")
holder.bindInfo(getItem(position))
}
// override fun getItemCount(): Int {
// return list.size
// }
}
위 ListAdapter는 템플릿 형은 아니다 적절히 가져가서 dto 등을 바꿔줘야 한다. 참고로 databinding을 사용하여
fun bindInfo(data: SearchResult){
binding.apply{
searchResult = data
}
}
binding을 간략히 하였다.
또한 DiffUtil을 구현할 수 있는 ListAdapter를 사용했기 때문에 notifyDataSetChanged()를 할 필요가 없다.
// !! 필수 !!
// databinding의 lifycycle을 현재 fragment의 viewModel과 lifecyle을 맞춤
binding.lifecycleOwner = viewLifecycleOwner
라이프사이클을 뷰모델에 맞춰주어야 한다..!