绑定数据安卓的通用形式



我最近开始学习 Kotlin 中的视图模型和数据绑定。我创建了一个示例项目,在一个活动中创建了多个片段。我很想知道如何使用视图模型实现具有数据绑定的通用片段。我不确定这是否可能,或者我是否走在正确的道路上。我在网上搜索并找到了一些线索,但没有完整的解决方案。

链接1

链接2

到目前为止,我创建了一个抽象的BaseFragment。

abstract class BaseFragment<V : BaseViewModel> : Fragment()
{
lateinit var binding: FragmentHomeBinding
lateinit var viewModel : V

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, getContentView(), container, false)
val view = binding.root
//here data must be an instance of the class MarsDataProvider
viewModel = ViewModelProviders.of(this).get(viewModel.javaClass)
setupUI()
binding.viewModel = viewModel
return view
}
abstract fun setupUI()
abstract fun getContentView() : Int
}

这是 HomeFragment 的代码

class HomeFragment : BaseFragment<HomeViewModel>() {
override fun setupUI() {
viewModel.errorMessage.observe(this, Observer {
errorMessage -> if(errorMessage != null) showError(errorMessage) else hideError()
})
}
override fun getContentView(): Int {
return R.layout.fragment_home
}

private var errorSnackbar: Snackbar? = null

private fun showError(@StringRes errorMessage:Int){
Log.d("anton","showError")
errorSnackbar = Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_INDEFINITE)
errorSnackbar?.setAction(R.string.retry, viewModel.errorClickListener)
errorSnackbar?.show()
}
private fun hideError(){
Log.d("anton","hideError")
errorSnackbar?.dismiss()
}
}

这是我拥有的片段的XML布局之一

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="app.series.com.series4go.viewmodels.HomeViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:mutableVisibility="@{viewModel.getLoadingVisibility()}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/post_list"
android:layout_width="0dp"
android:layout_height="0dp"
app:adapter="@{viewModel.getPostListAdapter()}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

我不知道如何改变

lateinit var binding: FragmentHomeBinding

在 BaseFragment 中,它将是通用的,因为我需要初始化

binding = DataBindingUtil.inflate(inflater, getContentView(), container, false)  

编辑

在玩完这段代码之后,我得出了这个:

碱基片段:

abstract class BaseFragment<V : BaseViewModel, T : ViewDataBinding> : Fragment()
{
lateinit var binding: T
lateinit var viewModel : V
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, getContentView(), container, false)
val view = binding.root
viewModel = ViewModelProviders.of(this).get(getViewModelClass())
setupUI()
bindViewToModel()
return view
}
abstract fun setupUI()
abstract fun getContentView() : Int
abstract fun getViewModelClass() : Class<V>
abstract fun bindViewToModel()
}

家片段

类 HomeFragment : BaseFragment(( {

override fun bindViewToModel() {
binding.viewModel = viewModel
}

override fun setupUI(){
binding.postList.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
//here data must be an instance of the class MarsDataProvider
viewModel.errorMessage.observe(this, Observer { errorMessage ->
if (errorMessage != null) showError(errorMessage) else hideError()
})
}
override fun getContentView(): Int {
return R.layout.fragment_home
}

private var errorSnackbar: Snackbar? = null

private fun showError(@StringRes errorMessage:Int){
Log.d("anton","showError")
errorSnackbar = Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_INDEFINITE)
errorSnackbar?.setAction(R.string.retry, viewModel.errorClickListener)
errorSnackbar?.show()
}
private fun hideError(){
Log.d("anton","hideError")
errorSnackbar?.dismiss()
}
override fun getViewModelClass(): Class<HomeViewModel> {
return HomeViewModel::class.java
}

}

在这个解决方案中,我唯一不喜欢的是函数 bindViewToModel,每个扩展基本片段的片段都需要在所有片段中以相同的方式实现它。不确定如何将其移动到基本片段,因为基本片段不知道布局的任何变量(因为它是抽象的(。

我很高兴知道是否有地方可以改进此设计或解决此问题。

谢谢

编辑 2

按照 @Oya Canlı 的解决方案,我设法删除了抽象的绑定ViewToModel,这是最终代码,以防有人有兴趣使用它。

碱基片段:

abstract class BaseFragment<V : BaseViewModel, T : ViewDataBinding> : Fragment()
{
lateinit var binding: T
lateinit var viewModel : V
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, getContentView(), container, false)
val view = binding.root
viewModel = ViewModelProviders.of(this).get(getViewModelClass())
setupUI()
binding.setVariable(BR.viewModel, viewModel)
return view
}
abstract fun setupUI()
abstract fun getContentView() : Int
abstract fun getViewModelClass() : Class<V>
}

家片段

class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>() {
override fun setupUI(){
binding.postList.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
//here data must be an instance of the class MarsDataProvider
viewModel.errorMessage.observe(this, Observer { errorMessage ->
if (errorMessage != null) showError(errorMessage) else hideError()
})
}
override fun getContentView(): Int {
return R.layout.fragment_home
}

private var errorSnackbar: Snackbar? = null

private fun showError(@StringRes errorMessage:Int){
Log.d("anton","showError")
errorSnackbar = Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_INDEFINITE)
errorSnackbar?.setAction(R.string.retry, viewModel.errorClickListener)
errorSnackbar?.show()
}
private fun hideError(){
Log.d("anton","hideError")
errorSnackbar?.dismiss()
}
override fun getViewModelClass(): Class<HomeViewModel> {
return HomeViewModel::class.java
}
}

数据绑定类的泛型类型是ViewDataBinding。因此,您可以将绑定实例获取为:

val binding = DataBindingUtil.inflate<ViewDataBinding>(
inflater, getContentView(), container, false)

但是你不能像binding.viewModel那样设置viewModel,因为泛型绑定类的实例不会有一个名为setViewModel的setter。你可以改用的是通用的setter setVariable:

binding.setVariable(BR.viewModel, viewModel)

BR 是一个生成的类,其中包含用于数据绑定的所有变量。上面的这种方法不是类型安全的,它不会检查提到的 BR 变量是否确实位于特定的绑定类中。这就是为什么通常最好使用特定的二传手。但是,当您不知道特定的绑定类时,就像在您的案例中一样,这就是要走的路。

还可以使用类似的方法来编写可重用的回收器视图适配器。

最新更新