안드로이드 template 만들기

이영준·2023년 5월 4일
0

반복적인 액티비티, 프래그먼트 사용을 위한 템플릿을 만들어 사용하면 유용하다.

📌 BaseActivity

// 액티비티의 기본을 작성, 뷰 바인딩 활용
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) {
..

와 같이 사용할 수 있다.

📌 BaseFragment

// 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 클래스이다.

📌 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()
    }
}

📌 SharedPreferencesUtil

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

}

📌 SplashActivity

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

📌 ListAdapter

    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()를 할 필요가 없다.

📌 Fragment Databinding을 할 때 유의점

        // !! 필수 !!
        // databinding의 lifycycle을 현재 fragment의 viewModel과 lifecyle을 맞춤
        binding.lifecycleOwner = viewLifecycleOwner

라이프사이클을 뷰모델에 맞춰주어야 한다..!

profile
컴퓨터와 교육 그사이 어딘가

0개의 댓글