Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions base/src/main/java/com/bt/base/ui/BaseFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import androidx.annotation.MainThread
import androidx.appcompat.app.AlertDialog
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
Expand All @@ -26,24 +28,37 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder

abstract class BaseFragment<ViewBinding : ViewDataBinding, ViewModel : BaseViewModel> : Fragment() {

lateinit var viewBinding: ViewBinding
private var _viewBinding: ViewBinding? = null

// FIXME: restrict to protected modifier and correct throw expression (create new class?)
val viewBinding: ViewBinding
get() = _viewBinding ?: throw IllegalStateException(
"BaseFragment#viewBinding is only valid between BaseFragment#onCreateView(..) " +
"and BaseFragment#onDestroyView(..)"
)

abstract val viewModel: ViewModel

@get:LayoutRes
abstract val layoutRes: Int

// FIXME: this can be private
var loadingDialog: AlertDialog? = null

// FIXME: add final modifier
@CallSuper
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewBinding = DataBindingUtil.inflate(inflater, layoutRes, container, false)
return viewBinding.root
): View {
return DataBindingUtil.inflate<ViewBinding>(inflater, layoutRes, container, false).run {
_viewBinding = this
root
}
}

@CallSuper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

Expand All @@ -54,10 +69,16 @@ abstract class BaseFragment<ViewBinding : ViewDataBinding, ViewModel : BaseViewM
executePendingBindings()
}

observeLiveData()
}

@CallSuper
open fun observeLiveData() {
with(viewModel) {
exceptionEvent.observe(viewLifecycleOwner) {
it.getContentIfNotHandled()?.let {
when (val e = it.mapToExceptionItem(requireContext())) {
it.getContentIfNotHandled()?.let { cleanException ->
val view = viewBinding.root
when (val e = cleanException.mapToExceptionItem(requireContext())) {
is AlertExceptionItem -> {
showAlertException(e)
}
Expand Down Expand Up @@ -90,6 +111,15 @@ abstract class BaseFragment<ViewBinding : ViewDataBinding, ViewModel : BaseViewM
}
}

@CallSuper
override fun onDestroyView() {
super.onDestroyView()
// release view
_viewBinding = null
}

// FIXME: this can be private
@MainThread
fun showDialogLoading(cancelable: Boolean = false, canceledOnTouchOutside: Boolean = false) {
if (loadingDialog?.isShowing != true) {
MaterialAlertDialogBuilder(requireContext()).apply {
Expand All @@ -105,6 +135,8 @@ abstract class BaseFragment<ViewBinding : ViewDataBinding, ViewModel : BaseViewM
}
}

// FIXME: this can be private
@MainThread
fun hideDialogLoading() {
if (loadingDialog?.isShowing == true) {
loadingDialog?.dismiss()
Expand Down
79 changes: 55 additions & 24 deletions base/src/main/java/com/bt/base/ui/BaseRecyclerAdapter.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package com.bt.base.ui

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bt.base.BR
import java.util.concurrent.Executors

/**
Expand All @@ -22,41 +19,75 @@ abstract class BaseRecyclerAdapter<Item, ViewBinding : ViewDataBinding>(callback
.build()
) {

var data = listOf<Item>()
// not thread-safe
private var cachedListRef: List<Item>? = null

override fun submitList(list: MutableList<Item>?) {
data = list ?: listOf()
super.submitList(ArrayList<Item>(data))
this.submitList(list, null)
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<ViewBinding> {
return BaseViewHolder(
DataBindingUtil.inflate<ViewBinding>(
LayoutInflater.from(parent.context),
getLayoutRes(viewType),
parent,
false
).apply {
bindFirstTime(this)
}
)
override fun submitList(list: MutableList<Item>?, commitCallback: Runnable?) {
// sửa lỗi khi dùng adapter kết hợp với Flow hoặc LiveData emit phiên giá trị mới nhưng cùng
// tham chiếu
if (list === cachedListRef) {
notifyDataSetChanged()
} else {
super.submitList(list, commitCallback)
}
}

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): NewBaseViewHolder<Item, ViewBinding> {
return NewBaseViewHolder<Item, ViewBinding>(parent, getLayoutRes(viewType))
// TODO: Delete after migration
.apply { bindFirstTime(binding) }
}

override fun onBindViewHolder(holder: BaseViewHolder<ViewBinding>, position: Int) {
val item = getItem(position)
holder.binding.apply {
setVariable(BR.item, item)
bindView(this, item, position)
executePendingBindings()
// TODO: use NewBaseViewHolder#bind(Item) after migration
// holder.bind(item)
@Suppress("UNCHECKED_CAST")
(holder as? NewBaseViewHolder<Item, ViewBinding>)?.bind(item) {
bindView(
it,
item,
position
)
}
}

// TODO: Delete after migration
@Deprecated("")
@LayoutRes
abstract fun getLayoutRes(viewType: Int): Int

protected open fun bindFirstTime(binding: ViewBinding) {}
// TODO: Delete after migration
@Deprecated(
"Adapter is responsible for ViewHolder's construction",
ReplaceWith("BaseViewHolder(ViewGroup)")
)
protected open fun bindFirstTime(binding: ViewBinding) {
}

protected open fun bindView(binding: ViewBinding, item: Item, position: Int) {}
// TODO: Delete after migration
@Deprecated(
"Adapter is not responsible for binding new item logic of ViewHolder",
ReplaceWith("BaseViewHolder#bind(Item)")
)
protected open fun bindView(binding: ViewBinding, item: Item, position: Int) {
}

open class BaseViewHolder<ViewBinding : ViewDataBinding> constructor(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root)
// TODO: Migrate to NewBaseViewModel
// this class now acts as a transition class when migrating, because there
// maybe subclasses of this class in other commits which may cause code conflicts
@Deprecated("")
open class BaseViewHolder<ViewBinding : ViewDataBinding>
@Deprecated("ViewHolder decide how to create itself, will be replaced by another constructor")
constructor(
@get:Deprecated("This property should be protected")
val binding: ViewBinding,
) : RecyclerView.ViewHolder(binding.root)
}
25 changes: 19 additions & 6 deletions base/src/main/java/com/bt/base/ui/BaseRecyclerApdater2.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ import com.bt.base.BR
/**
* Not apply DiffUtil
*/
abstract class BaseRecyclerAdapter2<Item, ViewBinding : ViewDataBinding> : RecyclerView.Adapter<BaseRecyclerAdapter2.BaseViewHolder2<ViewBinding>>() {
// FIXME: I suggest rename to: StaticBaseRecyclerAdapter | ImmutableBaseRecyclerAdapter
abstract class BaseRecyclerAdapter2<Item, ViewBinding : ViewDataBinding>
: RecyclerView.Adapter<BaseRecyclerAdapter2.BaseViewHolder2<ViewBinding>>() {

private var data = listOf<Item>()
private val data = mutableListOf<Item>()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder2<ViewBinding> {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): BaseViewHolder2<ViewBinding> {
return BaseViewHolder2(
DataBindingUtil.inflate<ViewBinding>(
LayoutInflater.from(parent.context),
Expand All @@ -39,17 +44,25 @@ abstract class BaseRecyclerAdapter2<Item, ViewBinding : ViewDataBinding> : Recyc

override fun getItemCount(): Int = data.size

@Deprecated("see BaseRecyclerAdapter#getLayoutRes(Int)")
@LayoutRes
abstract fun getLayoutRes(viewType: Int): Int

protected open fun bindFirstTime(binding: ViewBinding) {}
@Deprecated("see BaseRecyclerAdapter#bindFirstTime(ViewBinding)")
protected open fun bindFirstTime(binding: ViewBinding) {
}

protected open fun bindView(binding: ViewBinding, item: Item, position: Int) {}
@Deprecated("see BaseRecyclerAdapter#bindView(...)")
protected open fun bindView(binding: ViewBinding, item: Item, position: Int) {
}

@Deprecated("Should use constructor instead")
fun setData(d: List<Item>) {
data = d
data.clear()
data.addAll(d)
notifyDataSetChanged()
}

// FIXME: migrate to NewBaseViewModel
open class BaseViewHolder2<ViewBinding : ViewDataBinding> constructor(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root)
}
39 changes: 39 additions & 0 deletions base/src/main/java/com/bt/base/ui/NewBaseViewHolder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.bt.base.ui

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import androidx.annotation.MainThread
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import com.bt.base.BR

// FIXME: should be abstract?
open class NewBaseViewHolder<in Item, Binding : ViewDataBinding>(
parent: ViewGroup,
@LayoutRes
private val layoutId: Int,
attachToParent: Boolean = false,
) : BaseRecyclerAdapter.BaseViewHolder<Binding>(
DataBindingUtil.inflate<Binding>(
LayoutInflater.from(parent.context),
layoutId,
parent,
attachToParent,
)
) {
@CallSuper
@MainThread
open fun bind(item: Item) {
binding.setVariable(BR.item, item)
binding.executePendingBindings()
}

// TODO: Delete after migrating
@Deprecated("This function will be deleted after transition", ReplaceWith("#bind(Item)"))
fun bind(item: Item, apply: (Binding) -> Unit) {
apply(binding)
bind(item)
}
}