在我的应用程序中,当我点击RecyclerView中的一个项目(问题)时,该项目使用Firebase RecyclerPagingAdapter从Firebase实时数据库中分页数据,它会显示在另一个片段中点击的项目的详细信息(使用导航组件)。第一次点击时效果很好,但是当我返回到前一个片段并在RecyclerView上第二次点击相同的项目时,项目的详细信息不会显示。
因为我使用安全args来将项目id传递给下一个片段,它用于查询Firebase实时数据库并检索要显示的详细信息,所以我决定将项目id记录到我的控制台onViewCreated()中,只是为了确保项目id在第二次单击时被传递,并且详细信息(添加问题的用户的姓名)正在从数据库中检索,但只是不显示。然后,我注意到一个奇怪的行为。
第一次单击时,条目id被记录到控制台,详细信息也被记录到控制台,片段显示详细信息。但是,在第二次单击时,条目id被记录到控制台(显示条目id正在按实际情况传递),但细节不会记录到控制台,也不会显示在片段中(因此片段显示为空)。奇怪的是,当我回到前面的片段时,我看到了两次显示的细节日志。
我注意到的另一件奇怪的事情是,除了最后一件物品,RecyclerView上的每件物品都有这种奇怪的行为。最后一个项目在第二次单击时显示其详细信息,但我单击的任何其他项目都不会显示。
我还注意到,当我返回时,日志显示了我之前单击过两次的每个项目的详细信息,即使我正在单击另一个项目
我将适配器从Firebase RecyclerPagingAdapter更改为Firebase RecyclerAdapter,一切正常。当我更改回使用Firebase RecyclerPagingAdapter时,同样的问题也存在。
这是我的代码中的错误还是FirebaseRecyclerPaginAdapter本身的错误。可能是什么问题?我能做些什么来解决它?
以下是FireBaseRecyclerPaginAdapter:
package com.colley.android.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.colley.android.R
import com.colley.android.databinding.ItemIssueBinding
import com.colley.android.model.Issue
import com.colley.android.model.Profile
import com.firebase.ui.database.paging.DatabasePagingOptions
import com.firebase.ui.database.paging.FirebaseRecyclerPagingAdapter
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.ValueEventListener
import com.google.firebase.database.ktx.database
import com.google.firebase.database.ktx.getValue
import com.google.firebase.ktx.Firebase
class IssuesPagingAdapter(
options: DatabasePagingOptions<Issue>,
private val context: Context,
private val currentUser: FirebaseUser?,
private val clickListener: IssuePagingItemClickedListener
) : FirebaseRecyclerPagingAdapter<Issue, IssuePagingViewHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IssuePagingViewHolder {
val viewBinding = ItemIssueBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return IssuePagingViewHolder(viewBinding)
}
override fun onBindViewHolder(viewHolder: IssuePagingViewHolder, position: Int, model: Issue) {
viewHolder.bind(currentUser, model, context, clickListener)
}
interface IssuePagingItemClickedListener {
fun onItemClick(issueId: String, view: View)
fun onItemLongCLicked(issueId: String, view: View)
fun onUserClicked(userId: String, view: View)
}
}
class IssuePagingViewHolder (private val itemBinding : ItemIssueBinding) : RecyclerView.ViewHolder(itemBinding.root) {
@SuppressLint("SetTextI18n")
fun bind(
currentUser: FirebaseUser?,
issue: Issue, context: Context,
clickListener: IssuesPagingAdapter.IssuePagingItemClickedListener) = with(itemBinding) {
//set issue title, body, timeStamp, contributions and endorsements count
issueTitleTextView.text = issue.title
issueBodyTextView.text = issue.body
issueTimeStampTextView.text = issue.timeStamp
contributionsTextView.text = issue.contributionsCount.toString()
endorsementTextView.text = issue.endorsementsCount.toString()
//check if userId is not null
issue.userId?.let { userId ->
//retrieve user profile
Firebase.database.reference.child("profiles").child(userId)
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val profile = snapshot.getValue<Profile>()
if (profile != null) {
//set the name of user who raised this issue
userNameTextView.text = profile.name
//set the school of the user who raised this issue
userSchoolTextView.text = profile.school
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
//retrieve user photo
Firebase.database.reference.child("photos").child(userId)
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val photo = snapshot.getValue<String>()
//set photo
if (photo != null) {
Glide.with(root.context).load(photo)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).into(userImageView)
} else {
Glide.with(root.context).load(R.drawable.ic_person).into(userImageView)
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
}
root.setOnClickListener {
if(issue.issueId != null) {
clickListener.onItemClick(issue.issueId, it)
}
}
root.setOnLongClickListener {
if(issue.issueId != null) {
clickListener.onItemLongCLicked(issue.issueId, it)
}
true
}
userNameTextView.setOnClickListener {
if(issue.userId != null) {
clickListener.onUserClicked(issue.userId, it)
}
}
}
}
以下是显示项目详细信息的片段:
package com.colley.android.view.fragment
import android.os.Bundle
import android.util.Log
import android.view.*
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.colley.android.R
import com.colley.android.adapter.IssuesCommentsRecyclerAdapter
import com.colley.android.databinding.FragmentViewIssueBinding
import com.colley.android.model.Comment
import com.colley.android.model.Issue
import com.colley.android.model.Profile
import com.colley.android.view.dialog.IssueCommentBottomSheetDialogFragment
import com.firebase.ui.database.FirebaseRecyclerOptions
import com.firebase.ui.database.ObservableSnapshotArray
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.auth.ktx.auth
import com.google.firebase.database.*
import com.google.firebase.database.ktx.database
import com.google.firebase.database.ktx.getValue
import com.google.firebase.ktx.Firebase
class ViewIssueFragment :
Fragment(),
IssuesCommentsRecyclerAdapter.ItemClickedListener,
IssuesCommentsRecyclerAdapter.DataChangedListener {
private val args: ViewIssueFragmentArgs by navArgs()
private var _binding: FragmentViewIssueBinding? = null
private val binding get() = _binding
private lateinit var dbRef: DatabaseReference
private lateinit var auth: FirebaseAuth
private lateinit var currentUser: FirebaseUser
private lateinit var recyclerView: RecyclerView
private lateinit var commentSheetDialog: IssueCommentBottomSheetDialogFragment
private var issue: Issue? = null
private var adapter: IssuesCommentsRecyclerAdapter? = null
private var manager: LinearLayoutManager? = null
private val uid: String
get() = currentUser.uid
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentViewIssueBinding.inflate(inflater, container, false)
recyclerView = binding?.issuesCommentsRecyclerView!!
return binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//initialize Realtime Database
dbRef = Firebase.database.reference
//initialize authentication
auth = Firebase.auth
//initialize currentUser
currentUser = auth.currentUser!!
//log item id
Log.d("Log itemId", args.issueId)
//get a query reference to issue comments //order by time stamp
val commentsRef = dbRef.child("issues").child(args.issueId)
.child("comments").orderByChild("commentTimeStamp")
//the FirebaseRecyclerAdapter class and options come from the FirebaseUI library
//build an options to configure adapter. setQuery takes firebase query to listen to and a
//model class to which snapShots should be parsed
val options = FirebaseRecyclerOptions.Builder<Comment>()
.setQuery(commentsRef, Comment::class.java)
.build()
//initialize issue comments adapter
adapter = IssuesCommentsRecyclerAdapter(
options,
currentUser,
this,
this,
requireContext())
manager = LinearLayoutManager(requireContext())
//reversing layout and stacking fron end so that the most recent comments appear at the top
manager?.reverseLayout = true
manager?.stackFromEnd = true
recyclerView.layoutManager = manager
recyclerView.adapter = adapter
dbRef.child("issues").child(args.issueId).addValueEventListener(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
issue = snapshot.getValue<Issue>()
if(issue != null) {
//listener for contrbutions count used to set count text
dbRef.child("issues").child(args.issueId)
.child("contributionsCount").addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val count = snapshot.getValue<Int>()
if(count != null) {
binding?.contributionsTextView?.text = count.toString()
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
//listener for endorsement counts used to set endorsement count text
dbRef.child("issues").child(args.issueId)
.child("endorsementsCount").addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val count = snapshot.getValue<Int>()
if(count != null) {
binding?.endorsementTextView?.text = count.toString()
}
}
override fun onCancelled(error: DatabaseError) {} }
)
//set issue title, body and time stamp, these don't need to change
binding?.issueTitleTextView?.text = issue?.title
binding?.issueBodyTextView?.text = issue?.body
binding?.issueTimeStampTextView?.text = issue?.timeStamp.toString()
//listener for user photo
dbRef.child("photos").child(issue?.userId.toString())
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val photo = snapshot.getValue<String>()
if(photo != null) {
context?.let { context -> binding?.userImageView?.let {
imageView ->
Glide.with(context).load(photo).into(
imageView
)
} }
} else {
context?.let { context -> binding?.userImageView?.let {
imageView ->
Glide.with(context).load(R.drawable.ic_profile).into(
imageView
)
} }
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
//listener for profile to set name and school
dbRef.child("profiles").child(issue?.userId.toString())
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val profile = snapshot.getValue<Profile>()
if (profile != null) {
//log name details to console
profile.name?.let { Log.d("Log Details", it) }
binding?.userNameTextView?.text = profile.name
binding?.userSchoolTextView?.text = profile.school
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
binding?.commentLinearLayout?.setOnClickListener {
commentSheetDialog = IssueCommentBottomSheetDialogFragment(
requireContext(),
requireView())
commentSheetDialog.arguments = bundleOf("issueIdKey" to args.issueId)
commentSheetDialog.show(parentFragmentManager, null)
}
binding?.endorseLinearLayout?.setOnClickListener {
//update contributions count
dbRef.child("issues").child(args.issueId).child("endorsementsCount")
.runTransaction(
object : Transaction.Handler {
override fun doTransaction(currentData: MutableData): Transaction.Result {
//retrieve the current value of endorsement count at this location
var endorsementsCount = currentData.getValue<Int>()
if (endorsementsCount != null) {
//increase the count by 1
endorsementsCount++
//reassign the value to reflect the new update
currentData.value = endorsementsCount
}
//set database issue value to the new update
return Transaction.success(currentData)
}
override fun onComplete(
error: DatabaseError?,
committed: Boolean,
currentData: DataSnapshot?
) {
if (error == null && committed) {
Toast.makeText(requireContext(), "Endorsed", Toast.LENGTH_SHORT)
.show()
}
}
}
)
}
//view profile when clicked
binding?.userImageView?.setOnClickListener {
val action = issue?.userId?.let { it1 ->
ViewIssueFragmentDirections.actionViewIssueFragmentToUserInfoFragment(it1)
}
if (action != null) {
parentFragment?.findNavController()?.navigate(action)
}
}
//view user profile when clicked
binding?.userNameTextView?.setOnClickListener {
val action = issue?.userId?.let { it1 ->
ViewIssueFragmentDirections.actionViewIssueFragmentToUserInfoFragment(it1)
}
if (action != null) {
parentFragment?.findNavController()?.navigate(action)
}
}
}
override fun onItemClick(comment: Comment, view: View) {
//expand comment
}
override fun onItemLongCLicked(comment: Comment, view: View) {
//create option to delete
//create option to respond
}
//view user profile
override fun onUserClicked(userId: String, view: View) {
val action = ViewIssueFragmentDirections.actionViewIssueFragmentToUserInfoFragment(userId)
parentFragment?.findNavController()?.navigate(action)
}
override fun onStart() {
super.onStart()
adapter?.startListening()
}
override fun onStop() {
super.onStop()
adapter?.stopListening()
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
override fun onDataAvailable(snapshotArray: ObservableSnapshotArray<Comment>) {
//dismiss progress bar once snapshot is available
binding?.issuesCommentProgressBar?.visibility = GONE
//show that there are no comments if snapshot is empty else hide view
//show recycler view if snapshot is not empty else hide
if (snapshotArray.isEmpty()) {
binding?.noCommentsLayout?.visibility = VISIBLE
} else {
binding?.noCommentsLayout?.visibility = GONE
binding?.issuesCommentsRecyclerView?.visibility = VISIBLE
}
}
}
下面是带有recyclerView的片段,显示了我是如何初始化适配器的:
package com.colley.android.view.fragment
import android.os.Bundle
import android.view.*
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.paging.LoadState
import androidx.paging.PagingConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.colley.android.R
import com.colley.android.adapter.IssuesPagingAdapter
import com.colley.android.databinding.FragmentIssuesBinding
import com.colley.android.model.Issue
import com.firebase.ui.database.paging.DatabasePagingOptions
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.auth.ktx.auth
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ktx.database
import com.google.firebase.ktx.Firebase
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class IssuesFragment :
Fragment(),
IssuesPagingAdapter.IssuePagingItemClickedListener {
private var _binding: FragmentIssuesBinding? = null
private val binding get() = _binding!!
private lateinit var dbRef: DatabaseReference
private lateinit var auth: FirebaseAuth
private lateinit var currentUser: FirebaseUser
private var adapter: IssuesPagingAdapter? = null
private var manager: LinearLayoutManager? = null
private lateinit var recyclerView: RecyclerView
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private val uid: String
get() = currentUser.uid
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//fragment can participate in populating the options menu
setHasOptionsMenu(true)
//initialize Realtime Database
dbRef = Firebase.database.reference
//initialize authentication
auth = Firebase.auth
//initialize currentUser
currentUser = auth.currentUser!!
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
menu.clear()
inflater.inflate(R.menu.isssues_menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.search_issues_menu_item -> {
Toast.makeText(context, "Searching issues", Toast.LENGTH_LONG).show()
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentIssuesBinding.inflate(inflater, container, false)
recyclerView = binding.issueRecyclerView
swipeRefreshLayout = binding.swipeRefreshLayout
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//get a query reference to issues
val issuesQuery = dbRef.child("issues")
//configuration for how the FirebaseRecyclerPagingAdapter should load pages
val config = PagingConfig(
pageSize = 30,
prefetchDistance = 15,
enablePlaceholders = false
)
//Options to configure an FirebasePagingAdapter
val options = DatabasePagingOptions.Builder<Issue>()
.setLifecycleOwner(viewLifecycleOwner)
.setQuery(issuesQuery, config, Issue::class.java)
.setDiffCallback(object : DiffUtil.ItemCallback<DataSnapshot>() {
override fun areItemsTheSame(
oldItem: DataSnapshot,
newItem: DataSnapshot
): Boolean {
return oldItem.getValue(Issue::class.java)?.issueId == newItem.getValue(Issue::class.java)?.issueId
}
override fun areContentsTheSame(
oldItem: DataSnapshot,
newItem: DataSnapshot
): Boolean {
return oldItem.getValue(Issue::class.java) == newItem.getValue(Issue::class.java)
}
})
.build()
//instantiate adapter
adapter = IssuesPagingAdapter(
options,
requireContext(),
currentUser,
this)
//Perform some action every time data changes or when there is an error.
viewLifecycleOwner.lifecycleScope.launch {
adapter?.loadStateFlow?.collectLatest { loadStates ->
when (loadStates.refresh) {
is LoadState.Error -> {
// The initial load failed. Call the retry() method
// in order to retry the load operation.
Toast.makeText(context, "Error fetching issues! Retrying..", Toast.LENGTH_SHORT).show()
//display no posts available at the moment
binding.noIssuesLayout.visibility = VISIBLE
adapter?.retry()
}
is LoadState.Loading -> {
// The initial Load has begun
// ...
swipeRefreshLayout.isRefreshing = true
}
is LoadState.NotLoading -> {
// The previous load (either initial or additional) completed
swipeRefreshLayout.isRefreshing = false
//remove display no posts available at the moment
binding.noIssuesLayout.visibility = GONE
}
}
when (loadStates.append) {
is LoadState.Error -> {
// The additional load failed. Call the retry() method
// in order to retry the load operation.
adapter?.retry()
}
is LoadState.Loading -> {
// The adapter has started to load an additional page
// ...
swipeRefreshLayout.isRefreshing = true
}
is LoadState.NotLoading -> {
if (loadStates.append.endOfPaginationReached) {
// The adapter has finished loading all of the data set
swipeRefreshLayout.isRefreshing = false
}
}
}
}
}
//set recycler view layout manager
manager = LinearLayoutManager(requireContext())
recyclerView.layoutManager = manager
//initialize adapter
recyclerView.adapter = adapter
swipeRefreshLayout.setOnRefreshListener {
adapter?.refresh()
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
//navigate to new fragment with issue id
override fun onItemClick(issueId: String, view: View) {
val action = HomeFragmentDirections.actionHomeFragmentToViewIssueFragment(issueId)
parentFragment?.findNavController()?.navigate(action)
}
override fun onItemLongCLicked(issueId: String, view: View) {
}
override fun onUserClicked(userId: String, view: View) {
val action = HomeFragmentDirections.actionHomeFragmentToUserInfoFragment(userId)
parentFragment?.findNavController()?.navigate(action)
}
}
点击之前
第一次点击后
第二次点击后
使用addListenerForSingleValueEvent
而不是addValueEventListener
从显示单击的项目详细信息的片段中的数据库中查询项目详细信息(问题)。否则,请删除onStop()
中的addValueEventListener
,以便在导航回上一个片段时,侦听器不再连接到数据库。
我正在使用React-useState钩子更新状态。单击表单提交按钮时,状态直到第二次单击才更新。我相信我需要使用useffect,但不确定如何使用onClick实现。
当我触摸列表视图中的项目时,应用程序崩溃 这是logcat 2020-03-15 20:26:50.123 19174-19174/com.zeroXmohamed。TN19 E/Minikin:无法获取cmap表大小!2020-03-15 20:26:50.158 19174-19202/com.zeroXmohamed。TN19 E/MemoryLeakMonitor orManager:Me
这是我现在面临的一个奇怪的问题。从我的应用程序中,我从JSON文件中从链接中获取数据,然后用Volley解析它。现在我想在一个看起来不起作用的RecyclerView上显示数据,我很高兴我的代码很好,但是有些东西似乎不起作用,我找不到它。 这是活动代码 HomeActivity.java: 这是我从适配器中扩展的布局,并从JSON文件中设置数据 和适配器类相关的代码,我猜: @Override p
任何人都知道当用户第二次单击按钮时如何显示间隙广告。我的意思是,当用户单击一次按钮时,广告不应出现,但每当用户再次单击同一按钮时,广告必须显示。。 这里显示了我在不同活动中调用的中间代码。
我正在使用ComboBox在JavaFX中编写一个程序,并为布局加载一个FXML。 当我第一次在一个只有几个项目(例如只有两个)的组合框中单击时,滚动条显示在右侧。再次打开后,滚动条不再显示。 我尝试了一些解决方案。一种有效的方法是直接在FXML中应用CSS,将单元格大小设置为固定值。但是代码中的解决方案(例如,在控制器中的初始化函数中)更适合我的情况。 谢谢你的帮助。
如何使用Espresso单击RecyclerView项目中的特定视图?我知道我可以使用以下方法单击位置0处的项目: 但我需要点击项目内部的特定视图,而不是项目本身。 提前道谢。 --编辑-- 更准确地说:我有一个RecolyerView(),其中的项目是CardView()。在每个CardView中,我有四个按钮(以及其他东西),我想单击一个特定的按钮()。 我想使用Espresso2.0的新功能