需要两次绑定 Adapter 到 RecyclerView 才能显示数据

Need to bind Adapter to RecyclerView twice for data to appear

我有一个 Android 应用程序,我在其中将服务列表绑定到 RecyclerView:

fragment.kt

 override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        mBinding = FragmentAllServicesBinding.inflate(inflater, container, false)
        mViewModel = ViewModelProvider(this).get(AllServicesViewModel::class.java)
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
        return binding.root
    }

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

    subscribeServices()
}


// Private Functions
private fun subscribeServices(){
    val adapter = ServiceAdapter()

    binding.RecyclerViewServices.apply {
        /*
        * State that layout size will not change for better performance
        */
        setHasFixedSize(true)

        /* Bind the layout manager */
        layoutManager = LinearLayoutManager(requireContext())

        this.adapter = adapter
    }

        viewModel.services.observe(viewLifecycleOwner, { services ->
            if(services != null){
                lifecycleScope.launch {
                    adapter.submitList(services)
                }
            }
        })

}

viewmodel.kt

package com.th3pl4gu3.mes.ui.main.all_services

import android.app.Application
import androidx.lifecycle.*
import com.th3pl4gu3.mes.api.ApiRepository
import com.th3pl4gu3.mes.api.Service
import com.th3pl4gu3.mes.ui.utils.extensions.lowercase
import kotlinx.coroutines.launch
import kotlin.collections.ArrayList

class AllServicesViewModel(application: Application) : AndroidViewModel(application) {

    // Private Variables
    private val mServices = MutableLiveData<List<Service>>()
    private val mMessage = MutableLiveData<String>()
    private val mLoading = MutableLiveData(true)
    private var mSearchQuery = MutableLiveData<String>()
    private var mRawServices = ArrayList<Service>()

    // Properties
    val message: LiveData<String>
        get() = mMessage

    val loading: LiveData<Boolean>
        get() = mLoading

    val services: LiveData<List<Service>> = Transformations.switchMap(mSearchQuery) { query ->
        if (query.isEmpty()) {
            mServices.value = mRawServices
        } else {
            mServices.value = mRawServices.filter {
                it.name.lowercase().contains(query.lowercase()) ||
                        it.identifier.lowercase().contains(query.lowercase()) ||
                        it.type.lowercase().contains(query.lowercase())
            }
        }

        mServices
    }

    init {
        loadServices()
    }

    // Functions
    internal fun loadServices() {

        // Set loading to true to
        // notify the fragment that loading
        // has started and to show loading animation
        mLoading.value = true

        viewModelScope.launch {
            //TODO("Ensure connected to internet first")

            val response = ApiRepository.getInstance().getServices()

            if (response.success) {
                // Bind raw services
                mRawServices = ArrayList(response.services)

                // Set the default search string
                mSearchQuery.value = ""
            } else {
                mMessage.value = response.message
            }
        }.invokeOnCompletion {
            // Set loading to false to
            // notify the fragment that loading
            // has completed and to hide loading animation
            mLoading.value = false
        }
    }

    internal fun search(query: String) {
        mSearchQuery.value = query
    }
}

ServiceAdapter.kt

    class ServiceAdapter : ListAdapter<Service, ServiceViewHolder>(
    diffCallback
) {
    companion object {
        private val diffCallback = object : DiffUtil.ItemCallback<Service>() {
            override fun areItemsTheSame(oldItem: Service, newItem: Service): Boolean {
                return oldItem.identifier == newItem.identifier
            }

            override fun areContentsTheSame(oldItem: Service, newItem: Service): Boolean {
                return oldItem == newItem
            }
        }
    }

    override fun onBindViewHolder(holder: ServiceViewHolder, position: Int) {
        holder.bind(
            getItem(position)
        )
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ServiceViewHolder {
        return ServiceViewHolder.from(
            parent
        )
    }
}

ServiceViewHolder.kt

    class ServiceViewHolder private constructor(val binding: CustomRecyclerviewServiceBinding) :
    RecyclerView.ViewHolder(binding.root) {
    fun bind(
        service: Service?
    ) {
        binding.service = service
        binding.executePendingBindings()
    }

    companion object {
        fun from(parent: ViewGroup): ServiceViewHolder {
            val layoutInflater = LayoutInflater.from(parent.context)
            val binding =
                CustomRecyclerviewServiceBinding.inflate(layoutInflater, parent, false)
            return ServiceViewHolder(
                binding
            )
        }
    }
}

这里的问题是,数据不会显示在屏幕上。

出于某些原因,如果我将片段的代码更改为:

viewModel.services.observe(viewLifecycleOwner, { services ->
            if(services != null){
                lifecycleScope.launch {
                    adapter.submitList(services)

                    // Add this code
                    binding.RecyclerViewServices.adapter = adapter
                }
            }
        })

然后数据显示在屏幕上。

有谁知道为什么我需要设置适配器两次才能正常工作?

我有另一个应用程序,我不需要设置两次,它就可以运行。出于某种原因,此应用无法正常运行。 (另一个应用程序与这个应用程序之间的唯一区别是,这个应用程序从 API 中获取数据,而另一个应用程序从 Room (SQLite) 数据库中获取数据)

不要再次添加适配器,而是尝试在 adapter.submitList(services)

之后调用 adapter.notifyDataSetChanged()

里面

binding.RecyclerViewServices.apply {
 ...
} 

this.adapter = adapter改为this.adapter = this@YourFragmentName.adapter

原因是,您命名的适配器变量 "adapter"RecyclerView.adapter 的 属性 名称冲突。您实际上不是第一次设置适配器。这非常狡猾,因为 lint 不会给出任何警告并且代码编译没有错误...


您可以将片段中的 "adapter" 变量重命名为类似 "servicesAdapter" 的名称,并很快使用

 binding.RecyclerViewServices.apply {
     adapter = servicesAdapter
    }