recyclerView child won't be clickable after putting recyclerView in SwipeRefreshLayout - android

I have a recyclerView which I implemented a listener for its child in ViewHolder class just like this:
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun binding(model: NewsAdapterModel) {
itemView.setOnClickListener {
onClick.onClick(model.id)
}
}
and use this code for my onBindViewHolder:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.binding(items[position])
}
also here is my xml code which contains recyclerView and a SwipeRefreshLayout:
<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>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".main.news.NewsFragment">
<ProgressBar
android:id="#+id/loading"
style="#style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="visible"
app:layout_constraintTop_toTopOf="parent" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/loading">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#+id/loading" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
</layout>
my problem is as soon as I put my recyclerView inside Swipe Refreshing layout, item of recycler view are not clickable anymore. when I remove swipe refresh layout it become clickable again,
how can I solve this issue?

You should use SwipeRefreshLayout as parent of your LinearLayout and notify recycle view on setOnRefreshListener, code as follows-
Main Layout File
<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></data>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/loading">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".main.news.NewsFragment">
<ProgressBar
android:id="#+id/loading"
style="#style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="visible"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#+id/loading" />
</LinearLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</layout>
Doing so you can choose to show progress base until your recycle view will be updated with new information.
SwipeRefreshListener
mySwipeRefreshLayout.setOnRefreshListener(
new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
Log.i(LOG_TAG, "onRefresh called from SwipeRefreshLayout");
// This method performs the actual data-refresh operation.
// The method calls setRefreshing(false) when it's finished.
myUpdateOperation();
}
}
);

Related

Unable to scroll RecyclerView with Child items

I have a RecyclerView where in each item i can add X child items, when a parent item is added my RecyclerView scrolls to bottom but the issue is that when lot of child items are added to the last element it's not scrolling to last child (obviously as i'm doing scrollToPosition of last RecyclerView item).
So how should i perform the scrolling in the way that it scrolls to even last child not only the parent item?
Here a gif of how it looks like.
XML
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:stackFromEnd="true"
android:paddingBottom="45dp"
tools:listitem="#layout/comanda_list" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fabInvia"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:backgroundTint="#00C853"
app:layout_anchor="#+id/bottomSheet"
app:borderWidth="0dp"
app:layout_anchorGravity="top|center"
app:srcCompat="#drawable/ic_baseline_send"
android:contentDescription="#string/verifica_gp" />
<include layout="#layout/bottom_sheet_pterm" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Code where i add a child item in my Adapter
public void addChild(int position, Varianti variante) {
comandeList.get(position).setVariant(variante);
notifyItemChanged(position);
}
onBindViewHolder where the child items are rendered
#Override
public void onBindViewHolder(#NonNull final ExampleViewHolder holder, final int position) {
final Comanda item = comandeList.get(position);
...
List<Varianti> variants = item.getVarianti();
if (variants != null && variants.size() > 0) {
for (Varianti v : variants) {
View vView = mInflater.inflate(R.layout.varianti_layout, holder.variantsContainer, false);
TextView nameTV = vView.findViewById(R.id.variant_name);
if (v.getState().equals("K") || v.getState().equals("KK")) {
nameTV.setTypeface(null, Typeface.BOLD);
}
if (!item.getState().equals("S")) {
nameTV.setTextColor(Color.parseColor("#FFFFFF"));
}
nameTV.setText(v.getDescrizione());
nameTV.setBackground(v.getDrawable());
holder.variantsContainer.addView(vView);
}
}
...
}
In activity after adding new item / child item
adapterComanda.addVariante(position, variante);
recyclerComanda.scrollToPosition(adapterComanda.getItemCount() - 1);
BottomSheet
<LinearLayout 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"
android:id="#+id/bottomSheet"
android:background="#FFFFFF"
android:layout_width="match_parent"
android:layout_height="450dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
app:behavior_hideable="false"
android:elevation="5dp"
app:behavior_peekHeight="60dp"
android:orientation="vertical">
... (different LinearLayouts)
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:gravity="bottom"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerViewTasti"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="50dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
tools:listitem="#layout/tasto_list"
app:spanCount="4"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" />
</LinearLayout>
</LinearLayout>
The issue with your code is it is Scrolling to the bottom, but the recycler view is going below the <include layout="#layout/bottom_sheet_pterm" />
Try setting a layout_anchor
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="45dp"
app:layout_anchor="#+id/fabInvia"
app:stackFromEnd="true" />
Or use a constraintlayout which constraints bottom to top of fabInvia.

PagedList.Config loads list in the middle with recycleview

My RecyclerView loads in the middle of the list instead of the most top item first. The list should load the topmost item. In this case, it's the "latest" post. Which I have set to the reverse layout to true and that works.
My RecyclerView
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:viewBindingIgnore="true">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/postsList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:padding="5dp"
tools:listitem="#layout/post_item" />
</FrameLayout>
My Post list fragment
// Set up FirebaseRecyclerAdapter with the Query
val postsQuery = database.child(Globals().POSTS_ROOT_PATH)
// This configuration comes from the Paging Support Library
// https://developer.android.com/reference/androidx/paging/PagedList.Config
val config = PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPrefetchDistance(5)
.setPageSize(10)
.build()
// The options for the adapter combine the paging configuration with query information
// and application-specific options for lifecycle, etc.
val options = DatabasePagingOptions.Builder<Post>()
.setLifecycleOwner(this)
.setQuery(postsQuery, config, Post::class.java)
.build()
adapter = object : FirebaseRecyclerPagingAdapter<Post, PostViewHolder>(options) {
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): PostViewHolder {
val inflater = LayoutInflater.from(viewGroup.context)
return PostViewHolder(inflater.inflate(R.layout.post_item, viewGroup, false))
}
override fun onBindViewHolder(viewHolder: PostViewHolder, position: Int, model: Post) {
val postRef = getRef(position)
// Set click listener for the whole post view
val postKey = postRef.key
// Log.d("PostListFragment", "post key id is: " + postKey)
viewHolder.itemView.setOnClickListener {
// Launch PostDetailActivity
val intent = Intent(activity, PostDetailActivity::class.java)
intent.putExtra(PostDetailActivity.EXTRA_POST_KEY, postKey)
startActivity(intent)
}
}
override fun onLoadingStateChanged(state: LoadingState) {
when (state) {
LoadingState.LOADING_INITIAL -> {
Log.d(TAG, "Initial data loading")
}
LoadingState.LOADING_MORE -> {
}
LoadingState.LOADED -> {
}
LoadingState.FINISHED -> {
Log.d(TAG, "All data loaded.")
}
LoadingState.ERROR -> {
Log.d(TAG, "Error occured while loading data")
}
}
}
override fun onError(databaseError: DatabaseError) {
super.onError(databaseError)
databaseError.toException().printStackTrace()
}
}
// Set up Layout Manager, reverse layout
manager = LinearLayoutManager(activity)
manager.reverseLayout = true
manager.stackFromEnd = true
recyclerList.layoutManager = manager
recyclerList.adapter = adapter
}
When I set the setPageSize() to a very large number then it loads the most top item. What do I need to do so this always loads the most top list item first?
Thanks
Edit:
post_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
tools:viewBindingIgnore="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="#+id/postTextLayout"
layout="#layout/post_text_include"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="2dp"
android:layout_marginTop="2dp"
android:layout_marginStart="2dp" />
<include
android:id="#+id/postThumbnailImg"
layout="#layout/post_video_thumbnail_include"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_alignParentLeft="true"
android:layout_below="#+id/postTextLayout"
android:layout_alignParentStart="true" />
<include
android:id="#+id/postAuthorLayout"
layout="#layout/post_author_thumbnail_include"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/postThumbnailImg"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
Sub layouts
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/postTitle"
style="#style/TextAppearance.AppCompat.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textStyle="bold"
android:textColor="#color/colorPrimaryDark"
tools:text="My First Post"
android:paddingStart="5dp"
android:paddingEnd="5dp" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:viewBindingIgnore="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="#+id/videoThumbnailImg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/main_background" />
/>
<RelativeLayout
android:layout_height="match_parent"
android:layout_width="match_parent">
<ImageView
android:id="#+id/playButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="centerCrop"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:src="#drawable/ic_baseline_play_arrow_24" />
</RelativeLayout>
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center_vertical"
android:orientation="horizontal">
<androidx.cardview.widget.CardView
android:layout_width="40dp"
android:layout_height="40dp"
app:cardCornerRadius="20dp">
<ImageView
android:id="#+id/postAuthorPhoto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="#drawable/ic_person_outline_40" />
</androidx.cardview.widget.CardView>
<TextView
android:id="#+id/postAuthor"
style="#style/Base.TextAppearance.AppCompat.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:gravity="center_vertical"
android:textColor="#color/colorPrimaryDark"
tools:text="someauthor#email.com" />
</LinearLayout>
You need to set height wrap_content rather than match_parent for sublayouts.

Android RecyclerView with ViewBinding - notifyItemChanged only works the first time

So I have my RecyclerView setup with ViewDataBinding and I'm showing a ProgressBar on click of a button inside the item. The ProgressBar shows and pops a dialog, on cancelling dialog I can notifyItemChanged to hide the ProgressBar, it only works the first time.
Here is my layout of the item:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<import type="android.view.View"/>
<variable
name="inProgress"
type="boolean" />
<variable
name="userModel"
type="com.smartsco.recaru.room.dataModels.UserModel" />
<variable
name="messageModel"
type="com.smartsco.recaru.room.dataModels.MessageModel" />
<variable
name="clickHandler"
type="com.smartsco.recaru.interfaces.OnClickHandler" />
</data>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<androidx.cardview.widget.CardView
android:id="#+id/ll_invoice"
android:layout_width="#dimen/ml_invoice_width"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginStart="8dp"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
app:cardBackgroundColor="#color/bg_ml_invoice"
app:cardCornerRadius="2dp"
app:cardElevation="3dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/bg_ml_invoice"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="4dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="#+id/ll_invoice_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="horizontal"
android:padding="8dp"
android:visibility="#{!inProgress && messageModel.InvoiceData.status.equalsIgnoreCase(`pending`) ? View.VISIBLE : View.INVISIBLE}"
android:weightSum="2">
<Button
style="#style/Button_Invoice_Positive"
android:id="#+id/btn_agree"
android:layout_width="match_parent"
android:layout_height="#dimen/btn_invoice_height"
android:layout_weight="1"
android:text="#string/agree"
android:onClick="#{(v)-> clickHandler.onClick(v)}"/>
<Space
android:layout_width="6dp"
android:layout_height="0dp"/>
<Button
android:id="#+id/btn_disagree"
style="#style/Button_Invoice_Negative"
android:layout_width="match_parent"
android:layout_height="#dimen/btn_invoice_height"
android:layout_weight="1"
android:text="#string/disagree"
android:onClick="#{(v)-> clickHandler.onClick(v)}" />
</LinearLayout>
<ProgressBar
android:id="#+id/pb_invoice"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerInParent="true"
android:padding="8dp"
android:visibility="#{inProgress ? View.VISIBLE : View.INVISIBLE}" />
</RelativeLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
</layout>
Here is how the ViewHolder looks like:
private class InvoiceMessageHolder extends RecyclerView.ViewHolder implements OnClickHandler {
private RowRecyclerViewChatInvoiceMessageBinding binding;
InvoiceMessageHolder(RowRecyclerViewChatInvoiceMessageBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void bind(MessageModel message, UserModel userModel) {
binding.setMessageModel(message);
binding.setUserModel(userModel);
binding.setClickHandler(this);
}
#Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_agree:
case R.id.btn_disagree:
binding.setInProgress(true);
invoiceClickHandler.onClick(v, getAdapterPosition(), binding.getMessageModel());
break;
}
}
}
as you can see I have created a variable inside the layout itself to toggle inProgress boolean which works perfectly except won't reset itself on 2nd call of notifyItemChanged.
Another weird behaviour I have witnessed is that sometimes I click on button inside one item and it shows progress bar in all the viewholders for that itemViewType.
P.S.On calling notifyItemChanged, the view animates as if it was notified of a change but is only effective the first time.
Thanks

Fragment takes time to load RecyclerView when the list is large

I have a RecyclerView implemented along with Paging to load a list from Room Database. The list works fine when the size is small. When the size reaches around 50 - 60, the list still works fine but when i switch to another fragment and then come back to the list, its blocks the UI for around 1.5 - 2 seconds which is super dull in user experience (See GIF below):
My code is as follows:
DAO
#Query("SELECT * FROM account_table WHERE userID = :userID")
fun getAll(userID: String): DataSource.Factory<Int, Account>
Repository
class AccountRepository private constructor(application: Application) {
private val database =
LockyDatabase.getDatabase(
application
)
private val accountDao = database.accountDao()
companion object {
#Volatile
private var instance: AccountRepository? = null
fun getInstance(application: Application) =
instance ?: synchronized(this) {
instance ?: AccountRepository(application).also { instance = it }
}
}
fun getAll(userID: String) = accountDao.getAll(userID)
}
adapter
class CredentialsPagingAdapter(
private val clickListener: ClickListener,
private val optionsClickListener: OptionsClickListener?,
private val isSimplified: Boolean
) : PagedListAdapter<Credentials, CredentialsViewHolder>(
diffCallback
) {
companion object {
private val diffCallback = object : DiffUtil.ItemCallback<Credentials>() {
override fun areItemsTheSame(oldItem: Credentials, newItem: Credentials): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Credentials, newItem: Credentials): Boolean {
return oldItem.equals(newItem)
}
}
}
override fun onBindViewHolder(holder: CredentialsViewHolder, position: Int) {
holder.bind(
clickListener,
optionsClickListener,
getItem(position),
isSimplified
)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CredentialsViewHolder {
return CredentialsViewHolder.from(
parent
)
}
}
viewModel
val accounts = Transformations.switchMap(_sort) {
when (true) {
it.name -> _accounts.sortByEntryName
it.username -> _accounts.sortByUsername
it.email -> _accounts.sortByEmail
it.website -> _accounts.sortByWebsite
it.authType -> _accounts.sortByAuthenticationType
else -> _accounts
}.toLiveData(pageSize = resources.getInteger(R.integer.size_paging_list_default))
}
fragment
private fun subscribeAccounts() {
val adapter = CredentialsPagingAdapter(
/* The click listener to handle account on clicks */
ClickListener {
navigateTo(
AccountFragmentDirections.actionFragmentAccountToFragmentViewAccount(
it as Account
)
)
},
/* The click listener to handle popup menu for each accounts */
OptionsClickListener { view, credential ->
view.apply {
isEnabled = false
}
createPopupMenu(view, credential as Account)
},
false
)
binding.RecyclerViewAccount.apply {
/*
* State that layout size will not change for better performance
*/
setHasFixedSize(true)
/* Bind the layout manager */
layoutManager = LinearLayoutManager(requireContext())
/* Bind the adapter */
this.adapter = adapter
}
viewModel.accounts.observe(viewLifecycleOwner, Observer {
if (it != null) {
/*
* If accounts is not null
* Load recyclerview and
* Update the ui
*/
lifecycleScope.launch {
adapter.submitList(it as PagedList<Credentials>)
}
updateUI(it.size)
}
})
}
Main Activity Layout
<?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">
<androidx.drawerlayout.widget.DrawerLayout
android:id="#+id/Drawer_Main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.main.main.MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/Layout_Coordinator_Main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/Toolbar_Main"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#drawable/custom_rounded_background_toolbar"
android:clipChildren="false"
android:outlineAmbientShadowColor="#color/colorShadowColor"
android:outlineSpotShadowColor="#color/colorShadowColor"
android:paddingStart="8dp"
android:paddingEnd="8dp"
app:contentInsetStartWithNavigation="0dp"
tools:targetApi="p">
...
</com.google.android.material.appbar.MaterialToolbar>
<androidx.core.widget.NestedScrollView
android:id="#+id/Nested_Scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
android:fillViewport="true">
<fragment
android:id="#+id/Navigation_Host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/navigation_drawer_main"
tools:ignore="FragmentTagUsage" />
</androidx.core.widget.NestedScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton ... />
<com.google.android.material.floatingactionbutton.FloatingActionButton ... />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="#+id/Navigation_View"
style="#style/Locky.Widget.Custom.NavigationView"
android:layout_width="280dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:clipToPadding="false"
android:paddingStart="0dp"
android:paddingEnd="16dp"
app:headerLayout="#layout/drawer_header"
app:itemTextAppearance="#style/Locky.Text.Body.Drawer"
app:menu="#menu/menu_drawer_main" />
</androidx.drawerlayout.widget.DrawerLayout>
</layout>
Fragment Account Layout:
<?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>
<import type="android.view.View" />
<import type="com.th3pl4gu3.locky_offline.repository.Loading.List" />
<variable
name="ViewModel"
type="com.th3pl4gu3.locky_offline.ui.main.main.account.AccountViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/Layout_Fragment_Account"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorOnSurface">
<!--
Recyclerview
-->
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/RecyclerView_Account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="#{ViewModel.loadingStatus==List.LIST ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone" />
<!--
Empty Views and group
-->
<androidx.constraintlayout.widget.Group
android:id="#+id/Empty_View"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="#{ViewModel.loadingStatus==List.EMPTY_VIEW ? View.VISIBLE : View.GONE}"
app:constraint_referenced_ids="Empty_View_Illustration,Empty_View_Title,Empty_View_Subtitle" />
<ImageView
android:id="#+id/Empty_View_Illustration" ... />
<TextView
android:id="#+id/Empty_View_Title" ... />
<TextView
android:id="#+id/Empty_View_Subtitle" ... />
<!--
Progress Bar
-->
<include
android:id="#+id/Progress_Bar"
layout="#layout/custom_view_list_loading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="#{ViewModel.loadingStatus==List.LOADING ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Recyclerview List Layout
<?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>
<import type="android.view.View" />
<variable
name="IsSimplifiedVersion"
type="Boolean" />
<variable
name="Credential"
type="com.th3pl4gu3.locky_offline.core.main.credentials.Credentials" />
<variable
name="ClickListener"
type="com.th3pl4gu3.locky_offline.ui.main.main.ClickListener" />
<variable
name="OptionsClickListener"
type="com.th3pl4gu3.locky_offline.ui.main.main.OptionsClickListener" />
</data>
<com.google.android.material.card.MaterialCardView
style="#style/Locky.ListCardView"
credentialCardConfiguration="#{Credential}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:clickable="true"
android:focusable="true"
android:onClick="#{() -> ClickListener.onClick(Credential)}">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/Credential_Logo"
configureLogo="#{Credential}"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginEnd="16dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/Barrier_Logo"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="#drawable/ic_locky_with_background_circle" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/Barrier_Logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="Credential_Logo" />
<TextView
android:id="#+id/Credential_Entry_Name"
style="#style/Locky.Text.Title6.List"
listTitleMessageCardEligibility="#{Credential}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:ems="11"
android:singleLine="true"
android:text="#{Credential.entryName}"
app:layout_constraintBottom_toTopOf="#+id/Credential_First_Subtitle"
app:layout_constraintStart_toEndOf="#id/Barrier_Logo"
app:layout_constraintTop_toTopOf="#+id/Credential_Logo"
app:layout_constraintVertical_chainStyle="spread_inside"
tools:text="This is an entry name and it can be very very very long" />
<TextView
android:id="#+id/Credential_First_Subtitle"
style="#style/Locky.Text.Subtitle.List.Primary"
setCredentialSubtitle="#{Credential}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:ems="13"
android:singleLine="true"
app:layout_constraintBottom_toTopOf="#+id/Credential_Second_Subtitle"
app:layout_constraintStart_toStartOf="#+id/Credential_Entry_Name"
app:layout_constraintTop_toBottomOf="#+id/Credential_Entry_Name"
app:layout_constraintVertical_chainStyle="spread"
tools:text="This is the very first subtitle and this can be very long too" />
<TextView
android:id="#+id/Credential_Second_Subtitle"
style="#style/Locky.Text.Subtitle.List.Secondary"
setCredentialOtherSubtitle="#{Credential}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:ems="14"
android:singleLine="true"
android:textColor="#color/colorAccent"
app:layout_constraintBottom_toBottomOf="#+id/Credential_Logo"
app:layout_constraintStart_toStartOf="#+id/Credential_Entry_Name"
app:layout_constraintTop_toBottomOf="#+id/Credential_First_Subtitle"
app:layout_constraintVertical_chainStyle="spread"
tools:text="This is the second subtitle and this can be very long too" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/Barrier_More_Options"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="start"
app:constraint_referenced_ids="Credential_More_Options" />
<ImageButton
android:id="#+id/Credential_More_Options"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:background="#drawable/custom_states_background_button_image"
android:onClick="#{(view) -> OptionsClickListener.onClick(view, Credential)}"
android:scaleType="centerCrop"
android:src="#drawable/ic_more_options"
android:visibility="#{IsSimplifiedVersion ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toBottomOf="#+id/Credential_Logo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/Barrier_More_Options"
app:layout_constraintTop_toTopOf="#+id/Credential_Logo" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</layout>
My paging version is 2.1.2
Can someone please help me on this. I tried several days looking for fix but nothing works.
I appreciate the help.
You must remove your NestedScrollView (Nested_Scroll) in your activity layout - you cannot put a vertical RecyclerView within a NestedScrollView.
A NestedScrollView expands every child in the vertical scroll direction to determine the maximum scroll distance. This means that it gives the RecyclerView an infinite height to expand into. This causes RecyclerView to inflate every element, defeating all view recycling and defeating the use of paging - given infinite height, it'll continue to ask Paging for more and more rows to fill the space.

How do you add multiple child layout views to a MaterialCardView dynamically from within a fragment?

On one of my fragment, I have a CardView, where I want to add multiple child views, based on a layout file. The number of child views being added will depend on how many items are in a List object (so I use a for loop to iterate over the list). When I add a view to the cardview, the app crashes with an error saying I have to remove child views before adding another view.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.mahaprasad.mahaprasad.models.DeliveryMethod" />
<import type="android.view.View" />
<variable
name="viewModel"
type="com.mahaprasad.mahaprasad.screens.checkoutflow.CheckoutSharedViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="#+id/nested_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="50dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.card.MaterialCardView
android:id="#+id/basket_products_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<!-- Layout Child Views Should go here -->
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.button.MaterialButton
android:id="#+id/checkout_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:insetBottom="0dp"
android:text="Place Order"
android:textAlignment="viewStart"
app:backgroundTint="#color/green_button_colour"
app:cornerRadius="0dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
Layout template
<?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="product"
type="com.mahaprasad.mahaprasad.models.Product" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/product_image"
android:layout_width="90dp"
android:layout_height="90dp"
android:maxWidth="100dp"
android:maxHeight="100dp"
android:src="#drawable/chedvo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:layout_editor_absoluteX="50dp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/product_name_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="5dp"
android:maxWidth="300dp"
android:textSize="18sp"
android:textColor="#color/colourBlack"
android:autoSizeMaxTextSize="18sp"
android:autoSizeMinTextSize="14sp"
android:autoSizeStepGranularity="1sp"
android:autoSizeTextType="uniform"
android:maxLines="1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="#id/product_image"
app:layout_constraintBottom_toTopOf="#id/product_size_text"
app:productName="#{product}"
app:autoSizeMaxTextSize="18sp"
app:autoSizeMinTextSize="14sp"
app:autoSizeStepGranularity="2sp"
tools:text="Chedvo (Farari Chedvoooooo)"
tools:targetApi="o" />
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/product_size_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:textSize="15sp"
app:layout_constraintTop_toBottomOf="#id/product_name_text"
app:layout_constraintStart_toEndOf="#id/product_image"
app:productSize="#{product}"
tools:text="6 per pack" />
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/product_price_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:textColor="#color/colourBlack"
android:textSize="15sp"
app:layout_constraintStart_toEndOf="#id/product_image"
app:layout_constraintTop_toBottomOf="#id/product_size_text"
app:layout_constraintBottom_toBottomOf="parent"
app:productBasketQuantity="#{product}"
tools:text="1 x £5.00" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
class ReviewOrderFragment : Fragment() {
private lateinit var viewModel: CheckoutSharedViewModel
private val args: ReviewOrderFragmentArgs by navArgs()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding: FragmentReviewOrderBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_review_order, container, false)
viewModel = activity?.run {
ViewModelProviders.of(this).get(CheckoutSharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
populateBasketProductsCard(inflater, binding.basketProductsCard)
binding.viewModel = viewModel
binding.lifecycleOwner = this
return binding.root
}
private fun populateBasketProductsCard(inflater: LayoutInflater, basketProductsCard: MaterialCardView) {
val productInfoView: View = inflater.inflate(R.layout.layout_review_order_product_card, null)
val basket = args.order.basket.basket
basket.forEachIndexed { index, product ->
val productsCardBinding: LayoutReviewOrderProductCardBinding = DataBindingUtil.inflate(
inflater, R.layout.layout_review_order_product_card, null, false)
productsCardBinding.product = product
basketProductsCard.addView(
productInfoView,
index,
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
)
}
}
}
Just add a LinearLayout inside the MaterialCardView and insert your child views to the layout directly. MaterialCardView extends CardView and CardView is designed to hold a single child view within itself.
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<LinearLayout
android:id="#+id/basket_products_card"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Layout Child Views Should go here -->
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
Why don't you set a Layout as the child view of the CardView and add your list elements programmatically to this Layout instead of adding them all to CardView?

Categories

Resources