【问题标题】:Need to bind Adapter to RecyclerView twice for data to appear需要将Adapter绑定到RecyclerView两次才能出现数据
【发布时间】:2021-02-01 18:37:04
【问题描述】:

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

片段.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) 数据库获取数据)

【问题讨论】:

  • 为什么绑定视图一个地方叫RecyclerViewServices,另一个地方叫RecyclerView?
  • 抱歉,提问时打错字了,我更正了。
  • 您是否尝试过从观察中删除lifecycleScope.launch
  • 是的,同样的问题
  • 原因很简单,但非常非常狡猾,因为 lint 没有指出问题所在。您实际上不是第一次设置适配器。在下面检查我的答案)

标签: android kotlin android-recyclerview android-adapter


【解决方案1】:

里面

binding.RecyclerViewServices.apply {
 ...
} 

更改 this.adapter = adapterthis.adapter = this@YourFragmentName.adapter

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


或者您可以将片段中的"adapter" 变量重命名为"servicesAdapter" 之类的名称

 binding.RecyclerViewServices.apply {
     adapter = servicesAdapter
    } 

【讨论】:

  • 我永远不会想到这一点。一百万年后不会。非常感谢!
【解决方案2】:

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

【讨论】:

  • 不,这不起作用。该列表未出现。你能解释一下为什么会这样吗?
猜你喜欢
  • 1970-01-01
  • 2012-06-11
  • 1970-01-01
  • 1970-01-01
  • 2018-11-15
  • 2022-07-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-08
相关资源
最近更新 更多