I've made an app for Android TV which displays list of items from Firebase.
Expected behaviour:
When any change to database is done, fetch database and update the view.
What's wrong?
Data is fetched and displayed correctly, up to the moment I lock device. Afterwards it immediately stops refreshing view only. I put some "Log.d" in my adapter class to print updated list, and list is updated as it should be. So, in conclusion, data is being fetched correctly, list is being updated, but view is not (it stays like list before device lock was active).
Code:
Callback from backend with changed list:
override fun onDataSetChanged(prepareList: List<Long>) {
mPrepareAdapter.setOnDataSetChanged(prepareList)
}
Adapter:
var prepareList: MutableList<Long> = ArrayList()
...
fun setOnDataSetChanged(list: List<Long>){
prepareList.clear()
prepareList.addAll(list)
this.notifyDataSetChanged()
Log.d("prepareListAdapter", prepareList.toString())
}
Note: I did not post lifecycle methods like onResume() etc. as they work correctly. Having said that, they only are supposed to remove data observers or add them to observe database changes. As mentionted above, data is fetched correctly for whole the time, even after device is locked and unlocked, but just view is not being updated.
Plus, I've set layout manager in onCreate(), before adapter is set.
Edit: after reading this post couple of times and focusing on bolded word "only" next to "view", it lead me to debug this app on mobile device and create a second adapter using FirebaseUI. Here are some notes from my tests:
Android TV has some kind of weird Activity Lifecycle, as it works properly on mobile devices
It is not data/method related bug. It's related to a view or a context.
As mentioned in a point above, it's not a data delivery problem, so calling NotifyDataSetChanged() in onResume() doesn't fix the problem, because it's (?) view-related problem.
Behaviour with FirebaseRecyclerAdapter on Android TV:
Application works fine as long as device is not locked. After device is locked and unlocked RecyclerView is being cleared, even though logs show that data is still fetched properly and should be displayed.
So, it sounds to me like after device relock, adapter loses its view reference, here's my onCreateViewHolder() code, analogical is in Kotlin class:
private LayoutInflater inflater;
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
if (inflater == null) {
Context context = viewGroup.getContext();
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
View view = inflater.inflate(R.layout.item_textview_prepare, viewGroup, false);
return new ViewHolder(view);
}
Creating new instance of adapter in onResume() (yay, we shouldn't do that, I know), passing context to adapter? Nothing helps, RecyclerView still stays blank.
So, the only difference againist extending RecyclerView.Adapter and FirebaseRecyclerAdapter is that, in the prior one the old list is being displayed, when in the latter View reference is being lost (? isn't?).
For the end: here's the funny thing. This works properly not on all the mobile devices, even working on the same Android version. The bug occurs on both real Android TV and Android TV emulator (one with Google APIs).
I know it's a quite a long post and a lot to read, I'm sorry for that, but do you have any ideas what can I do more to fix it? I've been trying to fix that for the third day and nothing more comes up to my mind.
In your onResume() method, do a notifyDataSetChanged(), since you lock the device, when you come back to it, it should trigger onResume() and then, you can refresh your view.
Related
My Android app has this flow of screens when launched:
Splash -> Chat Groups > Chat Screen (showing chat messages)
On Chat Screen I have my Custom RecyclerView Implemented.
On fresh launch (or after killing the app), I go to Chat Screen, it loads previous messages fine, and new incoming message is also seen when u r on this screen.
Now if I press Android's Back button few time to exit the app, and then relaunch the app and go to Chat Screen, previous messages appear fine BUT the new incoming message is not visible.
Important thing is, even if I don't go to Chat Screen the first time and close the app from Groups Screen, then relaunching and going to Chat Screen again causes the problem and I dont see new incoming Chat messages.
I have debugged it and all code is being executed fine. The incoming message is added to the list of RecycleView, and notifyDataSetChange() is being called, but onBindViewHolder() is not being called in this case, and that's why the list doesn't get updated.
The code is pretty lengthy, but if u still need to see it then I'll try to add.
This is driving me crazy, I am pretty sure it's a bug in Android.
If u can propose a workaround, like clearing the RecyclerView or Adapter somehow that it gets to same state as when i Kill the app and launch..
Here is the code:
//Initialize Recycler view
mMessageRecycler = findViewById(R.id.recyclerview_message_list)
mMessageRecycler?.layoutManager = LinearLayoutManager(this)
....
if (messagesAdapter == null) {
messagesAdapter = NewMessageListAdapter(this)
mMessageRecycler?.adapter = messagesAdapter
}
//Adapter
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position:Int) {
val message = messageList[position]
(holder as ReceivedMessageHolder).bind(message)
}
override fun getItemCount(): Int {
return messageList.size
}
...
//add new chat message. Breakpoint does hit this code
messageList.add(newMessage)
notifyDataSetChanged()
As it's impossible to tell what is going on without looking at a bigger picture, I'll give you a few pointers.
a. I suggest you attempt to use ListAdapter<T, K> as it forces you to provide a DiffUtil.ItemCallback implementation. This will allow you to avoid calling the expensive and extremely inefficient notifyDataSetChanged(); instead you will call adapter.submitList(...) and supply a List<T> with your data.
K is the ViewHolder type. Usually you use RecyclerView.ViewHolder (if I correctly recall or if you only have one viewHolder type then you can couple that there and use it directly. Otherwise you'll just have to "cast" your ViewHolders to be able to call their "bind" method.
b. As for "it doesn't work when I get back", this is a bit harder to detect, as we haven't seen how/where/when you fetch this data; are you using Android Coroutines? Are the list of messages stored in a repository relying on memory or database persistence? who updates this list?
As you can see, there are a few outstanding questions that we (the readers) cannot possibly infer given the information you've provided.
If you want to see the simplest example of a RecyclerView with ListAdapter, I often tend to link this sample I wrote because it shows how to put all pieces together.
c. You shouldn't need to do if (adapter == null) { // create it and set it } either. You can have:
class YourActivityOrFragment : ... {
private val adapter = YourAdapter()
override fun onViewCreated(...) {
yourRecyclerView.adapter = adapter
}
You can later set the data in the adapter once you have it, there's no need to delay the creation of this. If you're going to use a LinearLayoutManager, remember you can also set it directly in XML and avoid writing the code.
Finally I found the problem! It was due to some memory leak and threads issue.
At some point in the code i was re-initializing my Mqtt class, without checking if it is not null. So I just added a null check and it fixed
if (mqttMy == null) // added
mqttMy = MqttMy(context)
Context
So, I don't know if any of you has ever gone through the same situation but I've recently taken over an app and we have this RecyclerView on the main screen - because of an NDA I'll change a few things - that shows a list of apartments that you can rent - picture the AirBnB app - and if you tap on one of these apartment items you go to the apartment detail, where you have a bit more of functionality and features.
The thing is that we have way too many moving parts on the apartment list. For example, on each apartment ViewHolder you can:
Use a checkmark to specify if you are going to bring any pets with you.
A few UI items to specify how long are you going to stay.
An EditText to set how may people are going to come.
A Rent button that turns itself into a spinner and sends an API call.
A More Options button that expands the ViewHolder, showing a LinearLayout with yet more UI.
Picture something like this
This is actually a simpler example of what I really have. Let me tell you that it looks as if each ViewHolder could be a Fragment because of all the functionality that we have on each.
Now what's the problem here?
Recycling issues. If you scroll off, and scroll back to the same position you are supposed to keep the same state that you had on that ViewHolder, right? If you had checked a CheckButton that's supposed to be check. If you had written something on an EditText, that's supposed to be there. If you had expanded the More Options section, that's supposed to be expanded. You see where I'm going at?
What am I asking here?
Well, about feedback for a possible solution or improvement. I know what most of you would tell me here - because it is the same thing I thought at first - just move all that functionality into the apartment detail, keep that list as simple as possible. But it is not as simple, we have a large user base who is already used to this UI. Changing things so abruptly is not an option.
What do I have right now?
In my RecyclerView adapter I keep a collection of "State" objects which I use to save/restore the ViewHolder states, but it is getting way too big and way too complex. This may sound crazy, but it is there such thing as having a RecyclerList of Fragments? I just don't want to worry/bother about keeping the states of these ViewHolder anymore.
Notes
Sorry I haven't provided any code, but there's not much to show actually, as you may imagine the onBindViewHolder is just a humongous piece of code that sets the views with the data I fetch from the API plus the data that I store in these "State" objects. I save these "State" objects via the onViewDetachedFromWindows() hook from the adapter class that gets triggered when a ViewHolder scrolls off from screen. I wipe out these "State" objects when I fetch a new API response.
Any feedback is appreciated,
Thanks!🙇
Your post is vague in it's high-level description but I'll try to comment in a similar manner that may guide you towards solutions.
First, as was already mentioned Epoxy is a thing. As is adapter delegates. You may find those useful. However, you don't need a library to solve you problem - you need separation of concerns and architecture.
The thing is that we have way too many moving parts on the apartment list.
OK, so first suggestion is to stop having too many moving parts in the list. Each thing you listed could / should be it's own (custom) view that is driven by it's own ViewModel. A recycler view / view holder / adapter should be as stupid as possible. All those things should be doing is filling in boilerplate that Android requires. Actual logic should exist elsewhere.
If you scroll off, and scroll back to the same position you are supposed to keep the same state that you had on that ViewHolder, right?
No. Your ViewHolder should not maintain state. A ViewHolder holds views so Android doesn't have to re-inflate stuff over and over. It should not keep track of its state - it should be told what its current state is.
You should have a list of data objects (view models) that represent the current state of each item in the list. When you scroll off and back to the same position, you are supposed to re-bind the item that should be at that position to the view that represents it. Saving and clearing "state" objects should not be necessary - you should always have the current state on hand because it's the underlying data model driving your whole UI.
In my RecyclerView adapter I keep a collection of "State" objects which I use to save/restore the ViewHolder states, but it is getting way too big and way too complex
If something is too big and complex, break it down. Instead of having one giant-ass state object for each item, use composition. Make this item state have properties that represent pieces of the UI - PetModel, DateRangeModel, etc.
This may sound crazy, but it is there such thing as having a RecyclerList of Fragments? I just don't want to worry/bother about keeping the states of these ViewHolder anymore.
That does sound crazy because not only would this not solve your problem, you would probably actually make it significantly worse. You don't want to manage the state of a bunch of ViewHolders but you want to manage the states of a bunch of Fragments!? Bruh.
as you may imagine the onBindViewHolder is just a humongous piece of code that sets the views with the data I fetch from the API plus the data that I store in these "State" objects.
Again, break that up. You should not be slapping "data I fetched from the API" directly onto views. Invariably you will need to massage and transform raw data from an API before you display it. This should be handled by a dedicated object (again, ViewModel or some other structure). Again, views should be dumb. Tell them their state and that's it - don't do logic at this level.
Please read the Android Architecture Guide.
Also Google around for "Clean Architecture" - that seems to be all the range in Android these days.
And finally - here's some very rough pseudocode of how you could structure this to be more testable and maintainable.
From the bottom up:
ApiClient - responsible for just fetching the raw data from the API
endpoint or reporting an error.
ApiResponseModel - language-specific object representation
of the data you'll get from the API. Has info on the pet, dates,
guest count, etc. May contain submodels.
ItemDomainModel - client side representation of your data after transforming the data you'll get from the API.
Repository - uses the ApiClient to fetch the data as ApiResponseModel and transforms it into a ItemDomainModel object that makes more sense for your app.
ItemViewModel - Represents the UI state of a single item in the RecyclerView. Takes a ItemDomainModel instance and exposes the state of the UI based on the state of that model. This can be broken down if it's too complex (PetStateViewModel, DateRangeViewModel, GuestCountViewModel, etc)
ListViewModel - The top-level Android ViewModel that represents the state of the screen. Uses the Repository to fetch the data then constructs a list of ItemViewModels to feed into the RecyclerViewAdapter.
If you get those pieces in place, your view binding in the adapter should be stupid dumb:
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
// The adapter list should be a list of view models populated by the
// fragment after the ListViewModel returns a list of them from the fetch
val itemViewModel = itemViewModels[position]
// Populating this item view should just be a one-to-one mapping of the view model
// state - NO LOGIC. Dumb. Stupid. Tonto.
viewHolder.bringingPets.isChecked = itemViewModel.isBringingPets
viewHolder.guestCount.text = itemViewModel.guestCount
// ... etc, etc (if you use databinding this is a one-liner and even stupider)
// Set up your event listeners so interacting with this specific item in the list
// updates the state of the underlying data model
viewHolder.bringingPets.setOnCheckChanged { itemViewModel.isBringingPets = it.isChecked }
viewHolder.rentButton.onClickListener { itemViewModel.rentThis() }
// ... etc, etc
}
The goal is to do as little as possible here. Just update the state and wire up your callbacks that just delegate back to the ViewModel. Then, those UI states are driven by the logic in the view model. This is where you do business logic that determines how the UI should look.
class ItemViewModel(private val dataModel: ItemDomainModel) {
var isBringingPets: Boolean
get() = /* some business logic that determines if the checkbox is checked */
set(value) /* update underlying state and notify of changes */
// ... etc, etc, for guest count and other properties
fun rentThis() {
// Fire an event or update live data or invoke a callback that
// the fragment can use to respond
}
// ... etc, etc, for other functions that respond to UI events
}
In Summary
Refactor your code to break down the huge and complex logic into dedicated components that each have a simpler, specific focus, then compose them together to get the behavior you want. Good luck.
This is just a mock to give you some idea of what I am doing.
My recycler view has complex logic. Let me point out them,
View holder UI is complex.
Loading banner ads after each 5 view holders.
My data is coming from network and I have been using Room + Retrofit + Paging adapter.
User experience is very bad. I need some suggestions. I believe there are 2 things effecting my scrolling function.
xml ui inflation.
Loading admob ads in UI thread.(They want us to do it in ui thread. still I dont know why they do this crazy stuffs. )
I need some suggestions how can I improve and give some good user experience .
Since there is no source code, I'm unable to try to evaluate the exact cause of this. But I experienced something like this before, I have some possible solution for you.
1) Move long-running task away from UI thread:
I noticed that your data is coming from Room + Retrofit. By default, Room must operate in async manner, unless allowMainThreadQueries() is called. If you did called allowMainThreadQueries(), you can check your code if you accidentally trying to fetch data on UI thread.
2) Did you implement RecyclerView (RV) properly?
RV reduces the amount of xml inflation whenever possible and improve performance by reusing the inflated layout for the same view type. So, if you only have 2 view types as shown in your question, RV will only inflate 3-6 of your layout (even if you have e.g. 100 items in your list) and attaches it to ViewHolder and then bind and recycle the view with Item as you scrolled through the list.
However, RV may perform poorly if implemented wrongly. One example I experienced before is returning position as view type in RV adapter.
public class SampleAdapter extends RecyclerView.Adapter<RVItem.ViewHolder> {
private final List<Item> items = new ArrayList<>();
#Override
public int getItemViewType(int position) {
return position; //Never do this
return items.get(position).getType(); //Do this
}
}
So, you can try to check if any of your implementation/logic is wrong with RV.
3) Use Profiler in Android Studio
If none of the above suggestions resolve the issue. The last way I can think of is recording the trace using Profiler while u scrolling through the RV and trying to identify which call is time-consuming/blocking by analysis the trace.
More info: https://developer.android.com/studio/profile/android-profiler
Additional info
Lastly, may I know what do you mean by loading AdMob ads in UI thread. As far as I know, AdMob load and return ads asynchronously like UnifiedNativeAd. And only then you try to inflate and set your view using the data from UnifiedNativeAd on UI thread which is not really UI-blocking task
This is the first time that I'm using RxJava (as well as Android Room, although not as relevant to the problem). I have nested RecyclerViews in my app, where the first RV shows the day and the second RV shows the forecast for the day. The app allows the user to change their zip code. Once the zip code is changed, the data within the 2 RVs should be updated. Therefore, a new network request is made and the 2 RVs should update their data.
Again, this is my first time using RxJava. Therefore, I really don't know what the best observer/ovservable combo is best for my particular situation. I figured that a Flowable would work best because it will emit data each time the data changes, which is exactly what happens when the end user changes their zip code: a new network request is made and the Room DB is updated.
However, ever since I've updated my Dao to using Flowable instead of Single, the data is updated within the DB (I've checked using Stetho) but the app now has some serious jank to it: it looks like it is constantly trying to refresh the data and the screen is constantly refreshing.
Here's how I update the data within the first RV (which is contained inside of a fragment):
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
// A bunch of other initial setup
Flowable<List<LocalDate>> source = MyApp.getDatabase(this.getContext()).getDao().getUniqueDays();
source.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(uniqueDays -> {
mAdapter.refreshData(uniqueDays);
});
}
I follow the same pattern for the data that is within the second RV (which is nested within the first RV, so this is called within the first RV's adapter's onBindViewHolder()):
public void bind(LocalDate date, int position){
Flowable<List<ForecastCondition>> source = MyApp.getDatabase(mContext).getDao().getForecastForDay(date);
source.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.delaySubscription(5, TimeUnit.SECONDS)
.subscribe(conditions -> mAdapter.refreshData(conditions));
}
I had to add the delaySubscription(5, TimeUnit.SECONDS) because the forecasts weren't always showing up within the second RV, but they do with the delay.
I know that I'm definitely making some mistakes here, but I don't exactly know what. Again, I've never worked with this before so any assistance would be greatly appreciated. I've read that I should be using Disposable for my subscription, but I don't completely understand what that does nor how it may solve my problem. The bigger problem though is that I'm unsure of how to solve the jank/stutter issue. Many thanks to anyone that can provide help on this. Your next tasty, cold (virtual) beverage will be on me.
My outer RecyclerView crashes either with
IllegalArgumentException: Scrapped or attached views may not be recycled. isScrap:false isAttached:true...
or
IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
Like the title suggests I have an RecyclerView in the list item layout of the first RecyclerView. This layout is used to display messages and the
inner RecyclerView to display attachments that come with the message. The inner RecyclerViews visibility is set to either GONE or VISIBLE depending whether the message has any attachments or not. The simplified outer list item layout looks like this
ConstraintLayout
TextView
TextView
TextView
RecyclerView
And the part of the adapter that handles the inner RecyclerView looks like this
private fun bindFiles(message: Message?) = with(itemView) {
if (message != null && message.attachments.isNotEmpty())
{
sent_message_attachments.setAsVisible()
sent_message_attachments.layoutManager = GridLayoutManager(this.context,Math.min(message.attachments.size,3))
sent_message_attachments.adapter = AttachmentAdapter(message.attachments)
sent_message_attachments.itemAnimator = null
sent_message_attachments.setHasFixedSize(true)
}
else{
sent_message_attachments.setAsGone()
sent_message_attachments.adapter = null
sent_message_attachments.layoutManager = null
}
}
The bug has something to do with the way I fetch the attachments in the inner adapter since once I disable the part that start the download process, everything is fine. There's no problem when loading images from the device, but once I start the download process, everything goes to hell. This is the part that handles images and kicks off the download process in the inner adapter. I have functions for videos and for other file types that are pretty much the same exact thing but use slightly different layout.
private fun bindImage(item: HFile?) = with(itemView) {
if (item != null)
{
if (item.isOnDevice && !item.path.isNullOrEmpty())
{
if (item.isGif)
{
attachment_image.displayGif(File(item.path))
}
else
{
attachment_image.displayImage(File(item.path))
}
}
else
{
//TODO: Add option to load images manually
FileHandler(item.id).downloadFileAsObservable(false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ progress ->
//TODO: Show download process
},
{ error ->
error.printStackTrace()
//TODO: Enable manual retry
},
{ notifyItemChanged(adapterPosition)} //onComplete
)
}
}
}
I use the same structure as above in my DiscussionListAdapter to load discussion portraits (profile pictures etc.) and it does not have the same issue.
These are the extensions functions used to inflate the viewHolders and to display the images
fun ViewGroup.inflate(layoutRes: Int): View
{
return LayoutInflater.from(context).inflate(layoutRes, this, false)
}
fun ImageView.displayGif(file:File){
GlideApp.with(context).asGif().load(file).transforms(CenterCrop(), RoundedCorners(30)).into(this)
}
fun ImageView.displayImage(file:File){
GlideApp.with(context).load(file).transforms(CenterCrop(), RoundedCorners(30)).into(this)
}
I've been on this for the past couple of days and just can't get my head around it. Any help in any direction is greatly appreciated. I know my explanations can be a bit all over the place so just ask for clarification when needed :)
UPDATE
I have now been able to produce this with a GridLayout as well as with RecyclerView. It's safe to assume that the nested RecyclerViews were not the culprit here. I even tried to ditch the Rx-piece that handled loading the images and created an IntentService for the process, but the same crashes still occur.
With GridLayout I mean that instead of having another adapter to populate the nested RecyclerView I use only one adapter to populate the message and to inflate and populate views for the attachments as well and to attach those views to the nested GridLayout.
The crash happens when I start to download a file and then scroll the view, that is supposed to show the downloaded file, out of the screen. That view should get recycled but for some reason the download process (which in my test cases only takes around 100ms-400ms) causes the app to throw one of the two errors mentioned in the original question. It might be worth noting that I'm using Realm and the adapter takes in a RealmResults<Message> list as it's dataset. My presenter looks for changes in the list and then notifies the adapter when needed (changed due to the implementation of IntentService).
This is how I'm capable to reproduce this time and time again:
Open a discussion that has messages with attachments
Start to scroll upwards for more messages
Pass a message with an attachment and scroll it off screen while it's still loading
Crash
There is no crash if I stop and wait for the download to complete and everything works as intended. The image/video/file gets updated with a proper thumbnail and the app wont crash if I scroll that out of view.
UPDATE 2
I tried swapping the nested ViewGroup for a single ImageView just to see is the problem within the nestedness. Lo and behold! It still crashes. Now I'm really confused, since the DiscussionListAdapter I mentioned before has the same exact thing in it and that one works like a charm... My search continues. I hope someone, some day will benefit from my agony.
UPDATE 3
I started to log the parent of every ViewHolder in the onBindViewHolder() function. Like expected I got nulls after nulls after nulls, before the app crashed and spew this out.
04-26 21:54:50.718 27075-27075/com.hailer.hailer.dev D/MsgAdapter: Parent of ViewHolder: android.view.ViewOverlay$OverlayViewGroup{82a9fbc V.E...... .......D 0,0-1440,2168}
There's a method to my madness after all! But this just poses more questions. Why is ViewOverlay used here? As a part of RecyclerView or as a part of the dark magicians plans to deprive me of my sanity?
Sidenote
I went digging into RecyclerViews code to check if I could find a reason for the ViewOverlaymystery. I found out that RecyclerView calls the adapters onCreateViewHolder() function only twice. Both times providing itself as the parent argument for the function. So no luck there... What the hell can cause the item view to have the ViewOverlay as it's parent? The parent is an immutable value, so the only way for the ViewOverlay to be set as the parent, is for something to construct a new ViewHolder and supply the ViewOverlay as the parent object.
UPDATE 4
Sometimes I amaze myself with my own stupidity. The ViewOverlay is used because the items are being animated. I didn't even consider this to be an option since I've set the itemAnimator for the RecyclerView as null, but for some odd reason that does not work. The items are still being animated and that is causing this whole charade. So what could be the cause of this? (How I chose to ignore the moving items, I do not know, but the animations became very clear when I forced the app to download same picture over and over again and the whole list went haywire.)
My DiscussionInstanceFragment contains the RecyclerView in question and a nested ConstraintLayout that in turn contains an EditText for user input and a send button.
val v = inflater.inflate(R.layout.fragment_discussion_instance, container, false)
val lm = LinearLayoutManager(context)
lm.reverseLayout = true
v.disc_instance_messages_list.layoutManager = lm
v.disc_instance_messages_list.itemAnimator = null
v.disc_instance_messages_list.adapter = mPresenter.messageAdapter
This is the piece that handles the initialization of the RecyclerView. I'm most definitely setting the itemAnimator as null, but the animations just wont stop! I've tried setting the animateLayoutChanges xml attribute on the root ConstraintLayout and on the RecyclerView but neither of them worked. It's worth mentioning that I also checked whether the RecyclerView had an itemAnimator in different states of the program, and every time I check the animator, it is null. So what is animating my RecyclerView?!
I have faced the same issue
Try this in your child RecyclerView it works for me
RecyclerView childRC = itemView.findViewById(R.id.cmol_childRC);
layoutManager = new LinearLayoutManager(context);
childRC.setItemAnimator(null);
childRC.setLayoutManager(layoutManager);
childRC.setNestedScrollingEnabled(false);
childRC.setHasFixedSize(true);
now set your Adapter like this
ArrayList<Model> childArryList = new ArrayList<>();
childArryList.addAll(arrayList.get(position).getArrayList());
ChildOrderAdapter adapter = new ChildOrderAdapter(context, childArryList);
holder.childRC.swapAdapter(adapter, true);
hope this helps
I finally figured out what was causing this. In my DiscussionInstanceView I have a small view that is animated into and out of view with ConstraintLayout keyframe animations. This view only shows the download progress of the chat history and is used only once, when the discussion is first opened. BUT since I had a call to hiding that view every time my dataset got updated, I was forcing the ConstraintLayout to fire of an animation sequence thus making everything animate during the dataset update. I just added a simple check whether I was downloading the history or not and this problem got fixed.