5 votes

Android kotlin data-binding with fragments error:cannot find symbol import com.example.***.databinding.FragmentUserBindingImpl ;

Je suis relativement novice en kotlin et j'essaye de construire un projet avec un data binding dans certains fragments :

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

et le fichier de présentation xml fragment_user.xml ressemble à ceci :

<?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>

il existe également une classe d'adaptateur similaire et 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
        }
    }
}

les 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>

noter que le databinding a été activé dans le gradle et le point très important ici est que dans mon fragment et dans l'adaptateur cette ligne :

binding.viewModel =viewModel

signale une erreur de type comme celle-ci :

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

et lorsque je construis mon projet, l'erreur est la suivante :

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

2voto

Robert LaFondue Points 191

La première erreur est assez explicite : binding.viewModel attend un UserListViewModel nullable ? et obtient un UserListViewModel non nullable (voir kotlin null safety doc ). Vous pouvez essayer quelque chose comme ceci pour vous en débarrasser :

Déclarez votre viewModel comme

private var viewModel: UserListViewModel? = null

et de régler votre liaison de cette manière :

viewModel?.let{binding.viewModel = it}

Concernant la seconde erreur, vos déclarations semblent correctes, mais parfois le cache d'Android Studio est corrompu, essayez d'Invalider les Caches/Redémarrer, cela peut aider.

0voto

Juan Hurtado Points 172

Erreur de type. Required:MyUser.UserListViewModel ? Found:com.example.***.ui.MyUser.UserListViewModel

En fait, l'erreur indique que votre

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

Il suffit donc de transformer votre modèle de vue en un type nullable en ajoutant l'attribut ? simbol après son délacarion (notez que les types init tardifs n'autorisent pas les types nullables). essayez-le comme ceci

private var viewModel: UserListViewModel? =  null

En ce qui concerne la deuxième erreur, la bibliothèque de liaison de données doit être compilée afin d'autogénérer la classe de liaison, il suffit de reconstruire le projet pour que cette erreur disparaisse.

N'hésitez pas à utiliser ce modèle comme base afin d'éviter tout ce code passe-partout.

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

Il suffit de l'utiliser comme suit (vous voyez combien de code de base a disparu)

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

override fun onBindViewModel(viewModel: UserListViewModel?, binding: FragmentUserBinding?) {
    binding.viewModel = viewModel
}
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X