How to correctly scroll after item inserted into PagedListAdapter - android-paging

I am using the Android Arch PagedListAdapter class. The issue I have run into is that since my app is a chat style app, I need to scroll to position 0 when an item is inserted. But the PagedListAdapter finds the diffs and calls necessary methods on the background thread so it seems that there is no dependable way to call layoutManager.scrollToPosition(0)
If anyone has any ideas on how I could scroll at the right time, it would be greatly appreciated. Thanks!
Here is my Room Dao:
#Dao
abstract class MessagesDao {
#Query("SELECT ...")
abstract fun getConversation(threadId: String): DataSource.Factory<Int, Conversation>
}
and then the LivePagedListBuilder:
LivePagedListBuilder(database.messagesDao.getConversation(threadId), PagedList.Config.Builder()
.setInitialLoadSizeHint(20)
.setPageSize(12)
.setEnablePlaceholders(true)
.build()).build()

It turns out that there is this beautiful thing called AdapterDataObsever which lets you observe the PagedListAdapter item calls. Here is how I was able to use it to solve the problem.
conversationAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == 0) {
manager.scrollToPosition(0)
}
}
})

Related

Kotlin Multiplatform Compose ~ How to use events to control views

I have a Kotlin Multiplatform project where I want to use Events to control views.
The basic idea is this:
Buttons & Co fire an Event when clicked
These events get caught and handled by the responsible program components, which will in turn fire other events
Eventually, some kind of ViewEvent is fired, which is subscribed to by the ViewController
The ViewController then tells the program what should be drawn on the screen
In theory, that sounds like it should work. In practice, what happens is that while it gets to the point where the ViewController receives the event and reacts accordingly, the actual views are unaffected.
My ViewController looks like this:
import androidx.compose.runtime.Composable
import com.tri_tail.ceal_chronicler.events.OpenCharacterSelectionViewEvent
import com.tri_tail.ceal_chronicler.ui.main_view.MainView
import com.tri_tail.ceal_chronicler.ui.main_view.MainViewState
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
class ViewController {
private var mainViewState = MainViewState.TITLE
init {
val eventBus = EventBus.getDefault()
eventBus.register(this)
}
#Composable
fun draw() {
MainView(mainViewState)
}
#Subscribe
fun onOpenCharacterSelectionViewEvent(event: OpenCharacterSelectionViewEvent) {
mainViewState = MainViewState.CHARACTER
}
}
I debugged that, and was able to see that the mainViewState changes, as expected. However, the draw() function is never called again, and so the changed mainViewState never arrives in the MainView.
I've already tried making mainViewState a mutableStateOf(mainViewState), but that didn't change anything.
Furthermore, I can't just call draw() inside the onOpenCharacterSelectionViewEvent, because it is not #Composable, and adding that annotation to the method causes the build to fail.
At this point, I am not even sure whether what I am trying to do here can work this way. Can someone please help me out here?
I have also published a version of the code with the current non-working solution here: https://github.com/KiraResari/ceal-chronicler/tree/event-system
For my KMM project i use open source viewModel for KMM.
This one https://github.com/adeo-opensource/kviewmodel--mpp
I suggest transfer your MVC architecture to MVI and use this KMM viewModel to control your state as in usual android app.
Okay, so after worrying at this for several days, I have now come up with a solution that works.
Basically, the reason why it doesn't work as I tried it is that the frontend lives in its own little world, and it is very difficult for something from outside that world to affect it.
However, it can be done using delegates. Basically, what I called the ViewController in above is more of a MainViewModel, and it needs to look like this:
import com.tri_tail.ceal_chronicler.events.OpenCharacterSelectionViewEvent
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
class MainViewModel {
var state = MainViewState.TITLE
var updateState: ((MainViewState) -> Unit) = { }
set(value) {
field = value
updateState(state)
}
init {
val eventBus = EventBus.getDefault()
eventBus.register(this)
}
#Subscribe
fun onOpenCharacterSelectionViewEvent(event: OpenCharacterSelectionViewEvent) {
state = MainViewState.CHARACTER
updateState(state)
}
}
The other part of the magic happens in the MainView, where the state needs to be a remember with a mutableStateOf(..., policy = neverEqualPolicy()), and the delegate needs to be set like this:
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.*
import com.tri_tail.ceal_chronicler.models.main_view.MainViewModel
import com.tri_tail.ceal_chronicler.models.main_view.MainViewState
import com.tri_tail.ceal_chronicler.theme.AppTheme
import com.tri_tail.ceal_chronicler.ui.TitleScreen
import com.tri_tail.ceal_chronicler.ui.characters.DisplayCharacterSelector
#Composable
fun MainView(model: MainViewModel = MainViewModel()) {
var state by remember {
mutableStateOf(
model.state,
policy = neverEqualPolicy()
)
}
model.updateState = {
state = it
}
AppTheme {
when (state) {
MainViewState.TITLE -> TitleScreen()
MainViewState.CHARACTER -> DisplayCharacterSelector()
}
}
}
And that's all there is to it! Works like a charm, no extra libraries required.

Can you return a result back to Flutter from Native Kotlin Bridge

at the moment I have a Flutter App that when I press a button it opens a Native Kotlin activity(mainActivity), once this executes I automatically open a SecondActivity that extends AppCompatActivity, the issue becomes is that I want to return a bool from the SecondActivity back to Flutter.
At the moment when the process finishes i use "finish()" to close my Native code and return to flutter successfully but I'd like to pass a result to Flutter.
I know how to do it from the mainActivity as it uses the FlutterActivity directly and can pass "result.success()", but from the different activity I can't find a solution.
I was wondering if somebody can tell me how to pass the bool from the secondActivity to Flutter.
Thank you very much everyone in advance!
It works exactly the same. Store Result in class property and call one of its methods from onActivityResult when the second activity finishes. Something like this:
class YourPlugin ... {
...
private var pendingResult: Result? = null
...
override fun onMethodCall(#NonNull call: MethodCall, #NonNull result: Result) {
...
pendingResult = result
activity?.startActivityForResult(...)
...
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?): Boolean {
...
pendingResult?.success(<result>)
...
return false
}

Angular 2 drag and drop directive extremely slow

I am trying to implement a custom drag and drop directive. It works, but it is extremely slow, and I think the slowness can be tracked to Angular 2 because I've never encountered this slowness before. The slowness only occurs when I attach an event listener to the dragover or drag events (i.e. the events which are sent frequently), even if I do nothing but return false in them.
Here's my directive code:
import {Directive, ElementRef, Inject, Injectable} from 'angular2/core';
declare var jQuery: any;
declare var document: any;
#Directive({
selector: '.my-log',
host: {
'(dragstart)': 'onDragStart($event)',
'(dragover)': 'onDragOver($event)',
'(dragleave)': 'onDragLeave($event)',
'(dragenter)': 'onDragEnter($event)',
'(drop)': 'onDrop($event)',
}
})
#Injectable()
export class DraggableDirective {
refcount = 0;
jel;
constructor( #Inject(ElementRef) private el: ElementRef) {
el.nativeElement.setAttribute('draggable', 'true');
this.jel = jQuery(el.nativeElement);
}
onDragStart(ev) {
ev.dataTransfer.setData('Text', ev.target.id);
}
onDragOver(ev) {
return false;
}
onDragEnter(ev) {
if (this.refcount === 0) {
this.jel.addClass('my-dragging-over');
}
this.refcount++;
}
onDragLeave(ev) {
this.refcount--;
if (this.refcount === 0) {
this.jel.removeClass('my-dragging-over');
}
}
onDrop(ev) {
this.jel.removeClass('my-dragging-over');
this.refcount = 0;
}
}
Here's the relevant style sheet excerpt:
.my-log.my-dragging-over {
background-color: yellow;
}
As you can see all I'm doing is highlighting the element being dragged over in yellow. And it works fast when I don't handle the dragover event, however I must handle it to support dropping. When I do handle the dragover event, everything slows down to unbearable levels!!
EDIT I am using angular beta 2.0.0-beta.8
EDIT #2 I tried profiling the code using chrome's profiler, these are the results:
Look at the marked line, it is strangely suspicious...
EDIT #3 Found the problem: it was indeed due to Angular 2's change detection. The drag and drop operation in my case is done on a very dense page with a lot of bindings and directives. When I commented out everything except the given list, it worked fast again... Now I need your help in finding a solution to this!
Just went through some trouble with the same problem. Even with efficient ngFor code, drag and drop can still be crazy slow if you have a large number of draggable items.
The trick for me was to make all drag and drop event listeners run outside of Angular with ngZone, then make it run back in Angular when dropped. This makes Angular avoid checking for detection for every pixel you move the draggable item around.
Inject:
import { Directive, ElementRef, NgZone } from '#angular/core';
constructor(private el: ElementRef, private ngZone: NgZone) {}
Initializing:
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
el.addEventListener('dragenter', (e) => {
// do stuff with e or el
});
...
On drop:
el.addEventListener('drop', (e) => {
this.ngZone.run(() => {
console.log("dropped");
})
})
Thanks to everybody for this discussion.
End up with simple solution which works like a charm:
constructor(private cd: ChangeDetectorRef) {
}
drag(event: DragEvent): void {
this.cd.detach();
// Begin the job (use event.dataTransfer)
}
allowDrop(event: DragEvent): void {
event.preventDefault();
}
drop(event: DragEvent): void {
event.preventDefault();
this.cd.reattach();
// Do the job
}
Answering my own question (problem was solved).
The slowness problem was due to inefficient data bindings in my markup, which caused Angular to waste a lot of time calling functions on my view model. I had many bindings of this sort:
*ngFor="#a of someFunc()"
This caused Angular to be unsure whether data has changed or not, and the function someFunc was getting called again and again after every run of onDragOver (which is a about once every 350ms) even though data was not changing during the drag and drop process. I changed these bindings to refer to simple properties in my class, and moved the code that populates them where it was supposed to be. Everything started moving lightning fast again!
I had a similar issue recently. It was in an Angular 6 environment using reactive forms. This is how I solved it for my situation:
Basically and briefly, I turned off change detection on that component while dragging was taking place.
import ChangeDetectorRef:
import { ChangeDetectorRef } from '#angular/core';
inject it into the constructor:
constructor(private chngDetRef: ChangeDetectorRef) { //...
detach it on dragStart:
private onDragStart(event, dragSource, dragIndex) {
// ...
this.chngDetRef.detach();
// ...
reattach it on drop and dragEnd:
private onDrop(event, dragSource, dragIndex) {
// ...
this.chngDetRef.reattach();
// ...
private onDragEnd(event, dragIndex) {
// ...
this.chngDetRef.reattach();
// ...
If you have a lot of parent or layered components, you may have to do something about their change detection as well in order to see a substantial improvement.
This is a follow up to an old post, but drag and drop is "still an issue. My particular problem involved a page with over 130 components on it and drag and drop was abysmal. I tried the various suggestions offered in this and other posts with only minimal improvement.
Finally, I decided that rather than the ngZone solution, I would try changing (dragOver)="function()" to the native ondragover="event.preventDefault()". I let all the other event handlers (i.e. dragStart, dragEnter, dragLeave, dragDrop, dragEnd) go through Angular as was needed. My drag and drop response went from seconds to milliseconds.
It would be great anyone could provide an alternative dragOver event handler that bypasses change detection.
I had a similar issue, also my drag and drop became very slow when I did put multiple drag zones inside a *ngFor.
I solved this by changing the change detection strategy to OnPush of the child component.
Then on every time when an item get dragged, do markForCheck().
constructor(private changeDetectorRef: ChangeDetectorRef) {}
// Callback function
public onDrag() {
this.changeDetectorRef.markForCheck();
}
Issue for me was that Development mode was turned on even in production. When i compiled it with ng build --evn-prod drag and drop is suddenly blazing fast.
I had the same problem with drag & drop in my angular project - detectChanges(reattach(), deTached ..), outSide Angular (ngZone) couldn't solve this problem.
Now I solved this problem by using jquery , I bond events in constructor for my div content.
constructor() {
$(document).delegate('#jsDragZone', 'dragenter', function (e) {
console.log('here your logic')
});
}
in this way you can implement other events too (dragleave, drop, 'dragover'). It's worked very nice and fast for me.

DWT togglebutton: does it even work?

I'm not sure if we are just 20 people in the world using dart right now and maybe just 10% of us try to use widgets..anyway, I seem not to understand if I'm doing something wrong with DWT or there is a bug. Here is a very simple example. I don't understand why the event are not even fired.
void main() {
ui.HorizontalPanel panel = new ui.HorizontalPanel();
ui.ToggleButton t1;
t1= new ui.ToggleButton.fromText("click",handler: new event.ClickHandlerAdapter((event.ClickEvent e) {
hans(panel,t1);
} ));
ui.ToggleButton t3;
t3 = new ui.ToggleButton.fromText("click");
t3.addClickHandler(new event.ClickHandlerAdapter((event.ClickEvent event) {
window.alert("Stop poking me!");
}));
panel.add(t1);
panel.add(t3);
ui.RootLayoutPanel.get().add(panel);
}
void hans(ui.Panel panel,ui.ToggleButton button){
var iter = panel.iterator();
while (iter.moveNext()) {
var butt = iter.current;
if (butt is ui.ToggleButton){
if (butt != button) {
butt.setDown(true);
}
}
}
Not really an answer to your question, but something I've noticed:
Not sure if bug or by design, but seems like the first node of a Tree does not handle clicks properly. I've created a tree with two ui.Label(s) in it and the first ui.Label does not respond to clicks. No aparent reason for that.
If I insert these ui.Label(s) in reversed order the situation is still the same: the first ui.Label (the other ui.Label, this time) does not respond to clicks.
So, I've circumvented the trouble by adding a Label in the beginning as "/" which is absolutely useless but does not need to respond to any clicks anyway.

scala : double "selection changed" event raised for ListView component

here is the part of code:
val lsv_syns = new ListView[String]()
val scp_syns = new ScrollPane() {
listenTo(lsv_syns.mouse.moves,lsv_syns.selection)
reactions += {
case me: MouseExited => {
txf_mot.requestFocus()
}
case SelectionChanged(`lsv_syns`)=> {
println("sélection:"+lsv_syns.selection.items(0))
}
}
}
As you can see, the listView is in a scrollPane; don't pay attention to the mouseExited event, the interesting thing is the selectionChanged, which seems to be called twice when I only click on time on an other line, because the println is called two times.
thanks.
Well i also recently worked with a ListView and now that you mentioned it, it also does my calculation twice.
The answer seems to be related to mouse events. Following the stack trace SelectionChanged is called twice. One coming from the Java event MousePressed and one from MouseReleased.
When you change the selection with KeyEvents it is only called once.
My first (and I guess not nice) idea to avoid the problem would be to ignore one of the events:
reactions += {
case SelectionChanged(`lsv_syns`) if !lsv_syns.selection.adjusting => {
println("sélection:"+lsv_syns.selection.items(0))
}
}
Both ListSelection events share the same data except getValueIsAdjusting. So if you check for it you can avoid doing your stuff twice.
Warning: !lsv_syns.selection.adjusting will result in printing on key release and not on press!
If you put lsv_syns.selection.adjusting it will correspond to key press but it will also filter key events. As I said. Not nice at all...