需要两次绑定 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
}
我有一个 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
}