5

我对 kotlin 比较陌生,并尝试在某些片段中构建一个具有数据绑定的项目。我有一个名为 UserFragment 的片段,其中有一个 Recyclerview,如下所示:

class UserFragment : Fragment() {
  private lateinit var binding: FragmentUserBinding
  private lateinit var viewModel: UserListViewModel
  override fun onCreateView(
      inflater: LayoutInflater, container: ViewGroup?,
      savedInstanceState: Bundle?
  ): View? {
      // Inflate the layout for this fragment
      binding=DataBindingUtil.inflate(inflater,R.layout.fragment_user, container, false)
      binding.userRecycler.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)

      viewModel = ViewModelProviders.of(this).get(UserListViewModel::class.java)
      viewModel.errorMessage.observe(this, Observer {
              errorMessage -> if(errorMessage != null) showError(errorMessage) else hideError()
      })
      binding.mViewModel=viewModel
      return binding.root
      }

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
      super.onViewCreated(view, savedInstanceState)
      //start add activity
      val i= Intent(activity,AddUserActivity::class.java)
      userFab.setOnClickListener(View.OnClickListener {
          startActivity(i)
      })
  }
  private fun showError(@StringRes errorMessage:Int){
      errorSnackbar = Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_INDEFINITE)
      errorSnackbar?.setAction(R.string.retry, viewModel.errorClickListener)
      errorSnackbar?.show()
  }

  private fun hideError(){
      errorSnackbar?.dismiss()
  }
  }

xml 布局文件fragment_user.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" xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
                name="mViewModel"
                type="com.example.***.ui.User.UserListViewModel" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        <EditText
                android:id="@+id/userDateEditText"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:hint="@string/pick_date"
                android:background="@drawable/roundededittext"
                android:layout_marginEnd="8dp"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginStart="8dp"
                app:layout_constraintStart_toStartOf="parent"
                android:layout_marginTop="8dp"
                app:layout_constraintTop_toTopOf="parent"/>

        <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/userRecycler"
                android:layout_width="293dp"
                android:layout_height="475dp" android:layout_marginTop="8dp"
                app:layout_constraintTop_toBottomOf="@+id/userDateEditText"
                app:adapter="@{viewModel.getUserListAdapter()}"
                app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginEnd="8dp" app:layout_constraintStart_toStartOf="parent"
                android:layout_marginStart="8dp" android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent"/>

        <com.google.android.material.floatingactionbutton.FloatingActionButton
                android:id="@+id/userFab"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" android:layout_marginBottom="48dp"
                app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginEnd="8dp"/>
        <ProgressBar
                style="?android:attr/progressBarStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:mutableVisibility="@{viewModel.getLoadingVisibility()}"
                android:id="@+id/userProgressBar" app:layout_constraintEnd_toEndOf="parent"
                android:layout_marginEnd="140dp" app:layout_constraintStart_toStartOf="parent"
                android:layout_marginStart="8dp" android:layout_marginTop="8dp"
                app:layout_constraintTop_toTopOf="parent" android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.804"
                app:layout_constraintVertical_bias="0.499"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

还有一个类似的适配器类和item_user.xml:

class UserListAdapter : RecyclerView.Adapter<UserListAdapter.ViewHolder>() {
    private lateinit var userModelList:List<UserModel>

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserListAdapter.ViewHolder {
        val binding: ItemUserBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.item_user, parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: UserListAdapter.ViewHolder, position: Int) {
        holder.bind(userModelList[position])
    }

    override fun getItemCount(): Int {
        return if(::userModelList.isInitialized) userModelList.size else 0
    }

    fun updateUserList(userModelList:List<UserModel>){
        this.userModelList = userModelList
        notifyDataSetChanged()
    }

    class ViewHolder(private val binding: ItemUserBinding):RecyclerView.ViewHolder(binding.root){
        private val viewModel = UserViewModel()

        fun bind(userModel: UserModel){
            viewModel.bind(userModel)
            binding.viewModel =viewModel
        }
    }
}

item - user.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="com.example.***.ui.MyUser.UserViewModel" />
    </data>

    <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingLeft="16dp"
            android:paddingRight="16dp">

        <TextView
                android:id="@+id/user_title"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:textStyle="bold"
                app:mutableText="@{viewModel.getUserTitle()}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

        <TextView
                android:id="@+id/user_description"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                app:mutableText="@{viewModel.getUserDesc()}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/user_title" />
    </android.support.constraint.ConstraintLayout>
</layout>

请注意,已在 gradle 中启用数据绑定,这里非常重要的问题是,在我的片段和适配器中,这一行:

binding.viewModel =viewModel

报告这样的类型不匹配:

Type mismatch.
Required:MyUser.UserListViewModel?
Found:com.example.***.ui.MyUser.UserListViewModel

当我构建我的项目时,错误如下:

error: cannot find symbol
import com.example.***.databinding.FragmentUserBindingImpl;
4

2 回答 2

2

第一个错误非常明确:binding.viewModel 需要一个可为空的 UserListViewModel?并获得一个不可为空的 UserListViewModel(请参阅kotlin null 安全文档)。你可以尝试这样的事情来摆脱它:

将您的 viewModel 声明为

private var viewModel: UserListViewModel? = null

并以这种方式设置您的绑定:

viewModel?.let{binding.viewModel = it}

关于第二个错误,您的声明似乎很好,但有时 Android Studio 的缓存已损坏,请尝试 Invalidate Caches/Restart,它可能会有所帮助。

于 2019-03-28T16:12:44.457 回答
0

类型不匹配。必需:MyUser.UserListViewModel?找到:com.example.***.ui.MyUser.UserListViewModel

基本上错误告诉你

binding.viewModel //is a nullable type and there for it expects a nullable 
//type to be assigned as well

因此,只需通过添加?将您的视图模型转换为可空类型 simbol 在其 delacarion 之后(注意后期初始化类型不允许可为空的类型)。像这样试试

private var viewModel: UserListViewModel? =  null

关于第二个错误数据绑定库需要编译才能自动生成绑定类,只需重建项目,这个错误就会消失。

请随意使用此模板作为基础,以避免所有样板代码

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class ContentView(@LayoutRes val id: Int)

fun ViewGroup.inflate(@LayoutRes layoutId: Int,
addContainer: Boolean = false): View {                                                     
    return LayoutInflater.from(context).inflate(layoutId,this,addContainer)      
}

@Suppress("UNCHECKED_CAST")
abstract class BaseFragment<Model : ViewModel, Binding : ViewDataBinding> : Fragment() {
/**
 * Data binding class variable all view elements declared on the
 * Xml file will be available within this instance if a view model
 * Is required for the xml to work we will need to bind it on @onBindViewModel
 */
protected lateinit var binding: WeakReference<Binding?>

/**
 * Fragments view model according to MVVM android architecture
 * Each fragment class should have one , in order to facilitate
 * Live Data and Binding library features, tho we can skip it
 */
protected lateinit var viewModel: WeakReference<Model?>

/**
 * Here is where most likely you will get not null data , both binding and
 * view model references can be destroyed by garbage collector
 * If this application reaches low memory levels
 *
 * This optional method is used to bind the required view model inside the
 * Xml file, this is optional to use tho is recommended
 * Bind them by calling the view model binding.customViewModel = viewModel
 */
protected open fun onBindViewModel(viewModel: Model?, binding: Binding?) {}

/**
 * There will be the occasion where custom params will be needed on view model's
 * Constructor in this case will want to override the default creation @see ViewModelFactory
 */
protected open fun onCreateViewModel(modelType: Class<Model>): Model? = if (modelType != ViewModel::class.java)
    ViewModelProviders.of(requireActivity()).get(modelType) else null

/**
 * Here we will inherit view model and binding values based on the class
 * Parameters and store them in global variables so any class extending
 * From Base activity has access to binding and view model instances by default
 */
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val layout = this.javaClass.annotations.find { it.annotationClass == ContentView::class } as ContentView?
        ?: throw RuntimeException("You annotate this class with @ContentView and provide a layout resource")

    container?.let { binding = WeakReference(DataBindingUtil.bind(it.inflate(layout.id))!!) }
        ?: run { binding = WeakReference(DataBindingUtil.bind(inflater.inflate(layout.id, null))) }

    viewModel = WeakReference(
        onCreateViewModel(
            (this.javaClass.genericSuperclass
                    as ParameterizedType).actualTypeArguments[0] as Class<Model>
        )
    )

    setHasOptionsMenu(true)
    onBindViewModel(viewModel.get(), binding.get())
    return binding.get()?.root
}
}

就这样使用它(看看有多少样板代码消失了)

@ContentView(R.layout.fragment_user)
class UserFragment: BaseFragment<UserListViewModel, FragmentUserBinding> {

override fun onBindViewModel(viewModel: UserListViewModel?, binding: FragmentUserBinding?) {
    binding.viewModel = viewModel
}
}
于 2019-03-28T16:29:49.607 回答