Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collectionview infinite scrolling upwards #10269

Open
FM1973 opened this issue Sep 22, 2022 · 36 comments
Open

Collectionview infinite scrolling upwards #10269

FM1973 opened this issue Sep 22, 2022 · 36 comments
Labels
area-controls-collectionview CollectionView, CarouselView, IndicatorView proposal/open
Milestone

Comments

@FM1973
Copy link

FM1973 commented Sep 22, 2022

Description

Collectionview is currenty only working top-down. I mean if you are using infininte scolling, this only works at the end of the list.
We have a "chat-section" in our app. In chat applications the last message is being shown at the bottom of the list an if you scroll up, older messages are being loaded.
Sorry, if this is already possible, we didn´t find a way to do so (except using 3rd party controls).

Public API Changes


var collectionView = new CollectionView();
collectionView.VerticalDirection = CollectionViewDirection.Upwards.

Intended Use-Case

We want to use this for a chat application. The last item is being shown at the bottom and if the users scrolls up, older items are being loaded.

@Eilon Eilon added the area-controls-collectionview CollectionView, CarouselView, IndicatorView label Sep 22, 2022
@PureWeen PureWeen added this to the Backlog milestone Sep 22, 2022
@ghost
Copy link

ghost commented Sep 22, 2022

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@SarthakGz
Copy link

@FM1973 You can subscribe to ItemAppearing command of CollectionView which executes for every row displayed when scrolling, detect last item appeared then load more data at zero index

@angelru
Copy link

angelru commented Nov 29, 2022

@FM1973
Currently I have a chat with CollectionView and I use this trick:

rotation="180"
ItemsUpdatingScrollMode="KeepScrollOffset"

In android I had to put this renderer:

  public class ExCollectionViewRenderer : CollectionViewRenderer
     {
         private int dy = 0;

         public ExCollectionViewRenderer(Context context) : base(context)
         {
         }

         public override void OnScrolled(int dx, int dy)
         {
             this.dy = dy;
             base.OnScrolled(dx, dy);
         }

         public override bool Fling(int velocityX, int velocityY)
         {
             return base.Fling(velocityX, Math.Abs(velocityY) * Math.Sign(dy));
         }

         protected override void OnElementChanged(ElementChangedEventArgs<ItemsView> elementChangedEvent)
         {
             base.OnElementChanged(elementChangedEvent);
         }
     }

I hope it works for you.

@FM1973
Copy link
Author

FM1973 commented Nov 30, 2022

@angelru
What a clever solution! Thanks a lot.
It works even without the custom renderer.

@angelru
Copy link

angelru commented Nov 30, 2022

@angelru What a clever solution! Thanks a lot. It works even without the custom renderer.

I think that the rendering was because in some version of android the scroll did not work well, check the version 8,9 and 10 of android.

Then your list has to be sorted backwards just like the group list.
Still, it seems like an inelegant solution to me.

@angelru
Copy link

angelru commented Nov 30, 2022

@FM1973Puede suscribirse al comando ItemAppearing de CollectionView que se ejecuta para cada fila que se muestra al desplazarse, detecta el último elemento que apareció y luego carga más datos en el índice cero

but how to scroll up?

@Engisan
Copy link

Engisan commented Aug 3, 2023

I use the 'Loaded' event on the item view from the CollectionView and then check from the codes whether the item is the first one in the collection and if so I load more objects:

<CollectionView ItemsSource="{Binding PrivateChats}"> <CollectionView.ItemTemplate> <DataTemplate> <Grid ColumnDefinitions="40,*,10" Loaded="PrivateMessageLoaded">...
Inside the PrivateMessageLoaded I get the PrivateChat entity and check whether this is the first one in the collection of the chat messages, if so, i load more items and put them into the collection (from the index 0 ofc)

@angelru
Copy link

angelru commented Aug 3, 2023

I use the 'Loaded' event on the item view from the CollectionView and then check from the codes whether the item is the first one in the collection and if so I load more objects:

<CollectionView ItemsSource="{Binding PrivateChats}"> <CollectionView.ItemTemplate> <DataTemplate> <Grid ColumnDefinitions="40,*,10" Loaded="PrivateMessageLoaded">... Inside the PrivateMessageLoaded I get the PrivateChat entity and check whether this is the first one in the collection of the chat messages, if so, i load more items and put them into the collection (from the index 0 ofc)

But you don't scroll up, do you?

@Engisan
Copy link

Engisan commented Aug 3, 2023

I use the 'Loaded' event on the item view from the CollectionView and then check from the codes whether the item is the first one in the collection and if so I load more objects:
<CollectionView ItemsSource="{Binding PrivateChats}"> <CollectionView.ItemTemplate> <DataTemplate> <Grid ColumnDefinitions="40,*,10" Loaded="PrivateMessageLoaded">... Inside the PrivateMessageLoaded I get the PrivateChat entity and check whether this is the first one in the collection of the chat messages, if so, i load more items and put them into the collection (from the index 0 ofc)

But you don't scroll up, do you?

Of course I do scroll up.
I load first set of messages (e.g. 20) and scroll to the last one by code => to the bottom. Then, when you scroll up to older messages, the event "Loaded" is fired for the items that were not visible in the collection before. And therefore I can load even older messages. It works exactly as you would think and as whatsapp / messenger works

@angelru
Copy link

angelru commented Aug 4, 2023

I use the 'Loaded' event on the item view from the CollectionView and then check from the codes whether the item is the first one in the collection and if so I load more objects:
<CollectionView ItemsSource="{Binding PrivateChats}"> <CollectionView.ItemTemplate> <DataTemplate> <Grid ColumnDefinitions="40,*,10" Loaded="PrivateMessageLoaded">... Inside the PrivateMessageLoaded I get the PrivateChat entity and check whether this is the first one in the collection of the chat messages, if so, i load more items and put them into the collection (from the index 0 ofc)

But you don't scroll up, do you?

Of course I do scroll up. I load first set of messages (e.g. 20) and scroll to the last one by code => to the bottom. Then, when you scroll up to older messages, the event "Loaded" is fired for the items that were not visible in the collection before. And therefore I can load even older messages. It works exactly as you would think and as whatsapp / messenger works

but you have not rotated the CollectionView 180? can you upload a small sample?

@Engisan
Copy link

Engisan commented Aug 4, 2023

ChatSample.zip

Just created the sample for you :)

@FM1973
Copy link
Author

FM1973 commented Aug 4, 2023

@Engisan Nice solution. I will give it a try. Although I will do this in my viewmodel. Thanks!

@Engisan
Copy link

Engisan commented Aug 4, 2023

@Engisan Nice solution. I will give it a try. Although I will do this in my viewmodel. Thanks!

Sure, this one was just a sample, I did not bother with view models etc. I just wanted to show how I do it. Enjoy! :)

@angelru
Copy link

angelru commented Aug 4, 2023

@Engisan Another alternative to rotating the CollectionView! thank you!

@Engisan
Copy link

Engisan commented Aug 4, 2023

@Engisan Another alternative to rotating the CollectionView! thank you!

Rotating did not work for me because it rotated the contents as well, so my texts were upside down etc. (.NET 7). I could not rly use it... Did you manage to get it work?

@FM1973
Copy link
Author

FM1973 commented Aug 4, 2023

@Engisan Another alternative to rotating the CollectionView! thank you!

Rotating did not work for me because it rotated the contents as well, so my texts were upside down etc. (.NET 7). I could not rly use it... Did you manage to get it work?

Yes, I managed to rotate everything. The problem is, this only works on Android and ends up in a ton of calculation exceptions on IOS

@angelru
Copy link

angelru commented Aug 4, 2023

You also have to rotate the list

@FM1973
Copy link
Author

FM1973 commented Aug 5, 2023

@Engisan
Sadly I couldn´t use your solution. I´m using DataTemplates with a DataTemplateSelector for my "chat". So I can´t use the Loaded event directly. I tried to use an EventToCommandBehavior, but this doesn´t work because the EventTocommandBehavior can´t use a relative source for the command.

So I changed my strategy and I am now using the scrolled event of the collectionview (with an EventToCommandBehavior).
The event args offer a vertical delta and the vertical offset. So when the delta is smaller or equal to 0 and the offset is smaller or equal to 0 then I check if there are some more messages to load and if, I insert these messages at index 0 in my collection.

If the delta and/or the offset are positive I just return and do nothing. Even if the number of messages equals the total number of messages I do nothing.

This works pretty well.

I can´t understand why I haven´t thought of solving the problem like this before.

You guided my to the right track. Thanks @Engisan!

@angelru
Copy link

angelru commented Aug 5, 2023

@Engisan Sadly I couldn´t use your solution. I´m using DataTemplates with a DataTemplateSelector for my "chat". So I can´t use the Loaded event directly. I tried to use an EventToCommandBehavior, but this doesn´t work because the EventTocommandBehavior can´t use a relative source for the command.

So I changed my strategy and I am now using the scrolled event of the collectionview (with an EventToCommandBehavior). The event args offer a vertical delta and the vertical offset. So when the delta is smaller or equal to 0 and the offset is smaller or equal to 0 then I check if there are some more messages to load and if, I insert these messages at index 0 in my collection.

If the delta and/or the offset are positive I just return and do nothing. Even if the number of messages equals the total number of messages I do nothing.

This works pretty well.

I can´t understand why I haven´t thought of solving the problem like this before.

You guided my to the right track. Thanks @Engisan!

Better solution than rotated CollectionView right? can you provide a small sample with dummy data?

@FM1973
Copy link
Author

FM1973 commented Aug 5, 2023

@angelru
Sure. I still have to work around some glitches. When I find time I will provide a sample.

@Engisan
Copy link

Engisan commented Aug 5, 2023

@angelru @FM1973 I tried Scrolled event and using vertical delta and the vertical offset as well.
But, for some reason, the offset or delta (cannot remember which one) was zero on the same item that was the first in the list on the beginning. So it did not really work well for me.
But yeah, I did not pay that much attention to that one since the Loaded event worked for me.
Plus I noticed that fast scrolling fast was kinda jerky due to the checks etc there... Anyway, it seems like there are some solutions tho :)

@FM1973
Copy link
Author

FM1973 commented Aug 6, 2023

@Engisan @angelru
I tried the chat sample on Ios. It doesn´t seem to work. Loaded gets called a couple of times, but when I reach the top, it´s not called anymore and no additional data get´s loaded. Am I missing something?

@Engisan
Copy link

Engisan commented Aug 6, 2023

@FM1973 Well, I did not try that on iOS, not yet. I would expect it to work, the Loaded event should be fired once you get the item into the visible scrolling area (it does not need to happen once you reach the top but once you scroll up so the item is processed by the system) and since it is the first item in the collection, you should be able to trigger new items loading... Weird. But as I said, I did not try that yet on iOS.
So maybe the Scrolled event and chacking offset + delta may be better solution then, if it works on both platforms...?

@Engisan
Copy link

Engisan commented Aug 7, 2023

By the way, the Scrolled event also contains property "FirstVisibleItemIndex" so whenever this is 0, it means you are at the top level... this works great on android, I will check the iOS version later.

@angelru
Copy link

angelru commented Aug 8, 2023

In flutter this is what is used:

       if (scrollController.position.pixels + 150 >=
           scrollController.position.maxScrollExtent) {
         loadMore();
       }

pixels: current position in pixels
maxScrollExtent - Represents the maximum position that can be scrolled to

@FM1973
Copy link
Author

FM1973 commented Aug 8, 2023

@Engisan "FirstVisibleItemIndex" is great, but in my tests you get index 0 a couple of times - even when loading the initial data. Even if you got i.e. a property "loading" which is set to true before loading the initial data and to false after loading, it can happen that you get index 0 a couple of times. Therefore I combinded this with vertical delta and the vertical offset. The delta is being used to check in which direction the user scrolls and the offset for checking if the top has been reached,

@Engisan
Copy link

Engisan commented Aug 8, 2023

@FM1973 maybe it depends on how you initially fill in the ObservableCollection... I just tested the soluition with Scrolled event and handling the FirstVisibleItemIndex and it works the best out of all solutions we had here (in my opinion). It works nicely even in iOS so I will stick with this one for now.

@FM1973
Copy link
Author

FM1973 commented Aug 8, 2023

@Engisan Yes. This may be. How do you fill it initially? I thought about using something like ObervableRangeCollection and adding the initial data using "AddRange". I will test this out. I then would add another function to ObervableRangeCollection. Something like AddRangeAtIndex - so I can add a list of items to index 0 before the property (Collection) notifies the ui that it has changed. There is a known bug though - #7237. You can get around this bug by adding a Task.Delay(xxx) before calling AddRange.

@angelru
Copy link

angelru commented Aug 8, 2023

It would be nice to leave an example of the last used way that works better, thanks!

@FM1973
Copy link
Author

FM1973 commented Aug 8, 2023

@angelru I´m working on it.

@FM1973
Copy link
Author

FM1973 commented Aug 8, 2023

@angelru You will find a small simple sample in the following repo. Just open the repo and try the last button on the main screen ('chat sample'). On Android this works pretty well. On IOS we have to scroll to the last visible message after inserting the new items. If you have any questions, let me know. @Engisan Anything I could do better?

@angelru
Copy link

angelru commented Aug 8, 2023

@angelruEncontrará una pequeña muestra simple en el siguiente repositorio . Simplemente abra el repositorio y pruebe el último botón en la pantalla principal ('muestra de chat'). En Android esto funciona bastante bien. En IOS tenemos que desplazarnos hasta el último mensaje visible después de insertar los nuevos elementos. Si tiene alguna pregunta, hágamelo saber.@Engisan¿Algo que pueda hacer mejor?

Nice sample, I see it works well, so do you think this is better than the rotate list/collectionview trick?

@FM1973
Copy link
Author

FM1973 commented Aug 8, 2023

@angelru good question. At least there is no need for Maui to do the rotation calculation. I think we could enhance the sample using the ObservableRangeCollection. If I find time I will enhance the sample using this type of collection. But my vacation is near, so I´m not sure if I will find time to do the enhanced version. At least you can use this version for Android and IOS while the rotation trick doesn´t work on IOS at all. There are a lot of calculation errors on IOS which result in a crash.

@Engisan
Copy link

Engisan commented Aug 8, 2023

@FM1973 nice, works fine. I do not see anything rly suspicious. And visually it is rly fine.

@angelru
Copy link

angelru commented Aug 9, 2023

@Engisan @FM1973 I haven't tried chat with rotation for a long time, since the app where it implements it doesn't exist.
I always use ObservableRangeCollection in my projects, if you don't implement it, when I can I will study your code further and see how to implement ObservableRangeCollection for chat. But yes, you need something that works on Android/iOS In the case of mobile, and without doing strange and complicated calculations, it's a pity that MAUI does not bring something like this by default, I seem to remember that a proposal was made to choose the scroll direction in CollectionView, but it was with Xamarin Forms.

@vtserej
Copy link

vtserej commented Oct 7, 2023

@Engisan Another alternative to rotating the CollectionView! thank you!

Rotating did not work for me because it rotated the contents as well, so my texts were upside down etc. (.NET 7). I could not rly use it... Did you manage to get it work?

Rotate the contents as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-controls-collectionview CollectionView, CarouselView, IndicatorView proposal/open
Projects
None yet
Development

No branches or pull requests

7 participants