绑定数据android的通用形式

Ant*_*kov 7 data-binding android mvvm kotlin

我最近开始学习 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
}
Run Code Online (Sandbox Code Playgroud)

这是 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()
    }

}
Run Code Online (Sandbox Code Playgroud)

这是我拥有的片段的 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>
Run Code Online (Sandbox Code Playgroud)

我不知道如何改变

lateinit var binding: FragmentHomeBinding
Run Code Online (Sandbox Code Playgroud)

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

binding = DataBindingUtil.inflate(inflater, getContentView(), container, false)  
Run Code Online (Sandbox Code Playgroud)

编辑

玩弄这段代码后,我来到了这个:

基础片段:

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

}
Run Code Online (Sandbox Code Playgroud)

首页片段

类 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
}
Run Code Online (Sandbox Code Playgroud)

}

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

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

谢谢

编辑 2

遵循@Oya Canl 的解决方案?我设法删除了抽象的 bindViewToModel 这是最后的代码,以防有人有兴趣使用它。

基础片段:

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>

}
Run Code Online (Sandbox Code Playgroud)

首页片段

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
    }

}
Run Code Online (Sandbox Code Playgroud)

Oya*_*nlı 10

数据绑定类的通用类型是ViewDataBinding。所以你可以得到你的绑定实例:

val binding = DataBindingUtil.inflate<ViewDataBinding>(
                    inflater, getContentView(), container, false)
Run Code Online (Sandbox Code Playgroud)

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

binding.setVariable(BR.viewModel, viewModel)
Run Code Online (Sandbox Code Playgroud)

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

您还可以使用类似的方法来编写可重用的 recyclerview 适配器。