UI is rendered but screen is not painted unless touched - react-native

We are able to confirm that the render() method of our component is getting invoked. We also see that the data that needs to be shown is correctly passed in via props. However, the actual phone display won't repaint the updated UI until it is touched.
Interestingly this only happens in the production build not on the development builds of the app. Sigh.
We have seen this in the past when updates are done from InteractionManager.runAfterInteractions, but in this case we have removed every use of runAfterInteractions and are still seeing this behavior.
Using react native 0.57 but also seeing the same issue on 0.58.
I can provide more specifics if needed, but wanted to know if anyone has seen anything like this before and what if anything they did to fix such an issue.

I had this issue also when the content that should repaint was inside a 'ScrollView'. I refactored my code using a 'FlatList' instead of a 'ScrollView' and the problem disappeared.

It was a struggle, but I figured out what was happening.
TL;DR
The solution is to wrap your redux action dispatches inside a setTimeout(..., 0) whenever you are doing UI updates from a function passed to runAfterInteractions or from a Realm callback.
Some quick background:
I was dispatching a redux action inside a Realm callback. The action would in turn cause the UI to re-render, except the rendered UI would not repaint on screen until it was touched.
Similar behavior was observed when you dispatch redux actions from a callback passed to InteractionManager.runAfterInteractions. This makes sense because callbacks passed to runAfterInteractions wait for UI to do its thing and are then executed as a setImmediate batch. These callbacks were intended for heavy background jobs that would otherwise block UI rendering. So if one were to do UI rendering in such callbacks those would themselves get halted like a background job!
Solution:
The point of the realm callback is to notify you that some data has changed, so that you can re-render your UI. However, the realm callback was behaving like one passed to runAfterInteractions and so UI re-rendering was getting held up. I took a leap of faith and decided to move the Redux action dispatching code to a different queue, namely setTimeout. Here's what the fix looked like:
...
// assume this is in a callback passed to runAfterInteractions
// or in a realm callback
// PUT YOUR REDUX ACTION DISPATCHES IN A setTimeout
// callback
setTimeout(() => {
getFilteredCartAreas(dispatch, realm)
dispatch(new ActionCartAreaCardChanged())
}, 0)
...
The fix is to do the UI rendering logic inside a callback passed to setTimeout. Notice the zero delay; the goal is to simply move logic to a different queue that is not held up by UI interactions.

Related

Will a JS root view always receive events from native after it has been removed from its superview?

My (heavily simplified) code looks like this:
// objc
self.currentSwipeUpView = [[RCTRootView alloc]
initWithBridge:_bridge moduleName:#"PhotoSwipeUpView"
initialProperties:nil
];
// elsewhere...
[self.currentSwipeUpView removeFromSuperview];
self.currentSwipeUpView = nil;
// js
function PhotoSwipedUpView() {
return <TextInput style={{flex: 1}} onChangeText={console.warn} />
}
AppRegistry.registerComponent('PhotoSwipeUpView', () => PhotoSwipeUpView)
self.currentSwipeUpView is removed from its superview and dereferenced. When this happen, is it possible for the JS thread to not receive a pending onChangeText event? Would it lead to console.warn to being with the new text?
I'm imagining a case where the event would have been sent to the JS thread after the RCTRootView was removed from its superview and dereferenced.
Furthermore, I'm curious if it's possible to be notified when a RCTRootView is removed from its superview from the JS side.
I'm using ARC and I'm not holding any other references to self.currentSwipeUpView.
Edit: I have two RCTRootViews. Only one of them will be unloaded, the other will persist across the state of the application. They use the same bridge, and therefore use the same bundle and JS environment.
Loading/Unloading JS Bundles
Your loaded js bundle is tied to the root view and is not persistent other than in that view.
Once you remove the root view from it's parent, the bundle is also unloaded. If the root view is removed and dereferenced, the text change listeners should also be removed and should not trigger.
The JSX in the bundle is used to ultimately setup native elements plus their listeners. The JSX is basically the script. The JSX with a RN TextInput basically results in a native UITextField. If the JSX specifies a listener, then a native listener for text changes is added to the native element instance.
Even if you had two root views loaded, they would operate independently from each other. If one root view is unloaded, the listeners associated with the TextInputs on the screen should no longer receive events. Your second Root View should be unaffected as it would have separate listeners setup for the TextInput.
Events/Timing issue
I suppose there is a small chance with perfect timing, and depending on where the removeFromSuperview is called, that the js could just receive the event before the view is completely removed and dereferenced, but if that has a possibility of affecting the functionality of your app, I would suggest using another pattern to interact with your data and remove the screen.
Possible Solution
It's hard to say without knowing what you're trying to do, but you could, for example, post a notification to close the React Native view using RCTEventEmitter/NativeEventEmitter, then have the React Native screen close itself with a native function that just removes it from the super view. This type of pattern would also answer your second question (in a sense), because you're effectively telling the RN screen before it should close allowing it to take any actions necessary before being removed.

how to set up background view update in durandal

I develop some admin pannel in durandal and have some view which show actively mutating data.
How can i force this view to repeatedly update, say, once in T seconds?
just give me a link to some sample/doc page/methods.
thanks
Actually, if you will use observable variables (which are part of KnockoutJs, and part of Durandal) in your application - you will see changes immediately. KnockoutJs Observables Documentation
If you are already use observables but need to make some delay between updating observable and refreshing UI - you can use Rate-limiting observable notifications which are delaying update notification for observable variables.

How can I allow a user adjust an NSSlider without pausing the application update loop?

NOTE: Updated below...
I have a cocoa desktop application which consists of a series of controls around a custom NSView. I am using displayLink to drive the updates.
When a user clicks on an NSControl (a slider, a button, a checkbox, a radio button) the application appears to freeze until the mouse is released. I can confirm in fact that the displayLink callback (getFrameForTime) is NOT firing during the time. If I create a timer, that also does not fire, both remain paused until the user releases the mouse, at which point the application resumes updating.
The control is bound, and if I update that value from another thread (for example, via a callback from a MIDI interface) the slider behaves as expected: it moves, the value updates and the application does not pause.
I feel like this should be a fairly obvious fix, but I'm stumped.
Checking "continuous" in IB does as advertised: sends the values continuously, but still exhibits this behavior (preventing the UI update) until the mouse is released.
This seems to be related specifically to mouseDown on NSControl? Why would this block, and do I really need to subclass all my UI elements to change this behavior (seems extreme)
DisplayLink is in its own thread, so why mouseDown on the main thread block it? If this is the case, given the injunction on updating the Cocoa UI from other than the main thread, how do I deal with it?
Any help much appreciated.
Update
Per #Nikolai's comments below, I can confirm that using an NSTimer and adding it to NSEventTrackingRunLoopMode does NOT block. However, I would really like to use CVDisplayLink which (according to the documentation) runs in it's own thread and should not be blocked in this way. Unlike CADisplayLink, I cannot find a way to explicitly assign a runloop to CVDisplayLink (it seems it doesn't work that way), so perhaps the new question should be:
Why does CVDisplayLink block on NSEventTrackingRunLoopMode?
When clicking on an NSControl the runloop mode goes from NSDefaultRunLoopMode to NSEventTrackingRunLoopMode, as long as the mouse is down. That means that only run loop sources (display link) and timers fire that have been added to this mode.
You can add timers to any mode by using -[NSRunLoop addTimer:forMode:]. For a display link the equivalent method is -[CADisplayLink addToRunLoop:forMode:].
To make your animation continue during event tracking you would do something like:
[myDisplayLink addToRunLoop:[NSRunLoop currentRunLoop]
forMode:NSEventTrackingRunLoopMode];
Your test project shows that you are calling a view's display method from within the display link's callback.
When commenting the display message out, the display link is called continuously even while moving the slider.
So what goes wrong is that when the runloop goes into event tracking mode, the call to display on the display link's thread blocks until the mouse is released and the run loop goes back to default mode. You can easily confirm this by putting a log statement before the call and one after it.
Why exactly that happens is not clear to me. What is clear is that it's illegal to call a view's methods from a background thread. You have to trigger the view's display by dispatching a setNeedsDisplay: on the main thread:
static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
dispatch_async(dispatch_get_main_queue(), ^{
[(__bridge MyCustomView*)displayLinkContext setNeedsDisplay:YES];
});
return kCVReturnSuccess;
}

Starting transitions before activate resolves

Is there a simple way to use Durandal's transitions to fade out an old view immediately, and fade in the new one once its activate function has resolved?
Here's some context:
I have a Durandal 1.2 SPA where most of the views' activate functions make service calls and return a promise as appropriate. This all works great, but transitions only run when loading is complete sometimes services can take few seconds to respond - leading to a poor user experience where you click on a link and it takes a few seconds before anything noticeable happens.
The solution is to animate away the old view immediately (before activate resolves) and then animate in the new view when activate is done. At the moment, every animated view corresponds to a route and I don't expect this to change. I've come up with and tested a few solutions that all work but seem less than ideal (and don't leverage Durandal's transition framework):
Manually animate away every view in its deactivate() and animate it back in via its viewAttached()
Bind the .page-host div's visibility to router.isNavigating (using a custom binding to handle the transition such as the fadeVisible example from the knockout site)
Manually subscribe to router.isNavigating and run custom logic when it changes
I've tried them all and so far I like the custom binding on the .page-host div the best since it involves the least amount of code, but it only works in my case because of my specific circumstances and isn't a generic solution.
This seems like exactly the sort of thing Durandal's transitions were created for. Is there a more elegant way to do this in Durandal using transitions (in 1.2 or the upcoming 2.0 release)?
I saw this question which seems to be asking something similar, but seems a little less specific and doesn't have a relevant answer.
Move your async logic out of activate and put it into viewAttached. This will allow the view to animate in immediately. Then, from viewAttached, you are safe to execute async code without breaking KO because the bindings have already been applied. (Note: viewAttached is renamed in 2.0 to attachedToParent.)
You can add handler for 'router:route:activating' event by adding next code into your shell.js:
router.on('router:route:activating').then(function () {
//fade out animation goes here;
//Usually that is changing of observable which will add/remove css class for your view (or shows loading overlay)
//Animation of view transition could be done by add class css3 animation.
});
And in case if you are using "loading" observable - in custom transition you can set this observable back to hide loading overlay:
if (context.bindingContext.$data && context.bindingContext.$data.isLoading && ko.isObservable(context.bindingContext.$data.isLoading )) {
composition.current.complete(function() {
context.bindingContext.$data.isLoading (true);
});
}

Animation and MVC principle

I always design my programs according to MVC principle, but fitting animation in is pain-in-the-ass.
I have implemented following scheme so far:
1) Model does number of actions [] and sends notifications to all listeners;
2) Upon reciving a notification View adds an animation to the queue.
It's workable approach, but it has one huge drawback — model and UI become unsyncronized. For example there are 10 actions already applied to the model, but UI is still in state, where 5 of them are applied.
This drawback forces animations to be coded uninterruptable, which is not good practice for UI design. Please, suggest how to resolve the issue.
You could update your model after the animation finished. This way, your animations are a representation that the user can use to determine when an actions is finished. E.g. after dragging an object to its destination, the object remains (model-side) on its old location until the animation finishes, only then is it moved to the new one.
That way, if the animation fails because the user interrupts it or an error ocurrs, your model will still be synchronized with the current state of your view.