How to prevent initial onFocus trigger on TextField during first composition - focus

Is there any way I can prevent onFocus trigger during the initial laying out of a composition?,
.onFocusChanged {
// initial pass triggers un-necessary onFocus callback with just `false` values
Log.e("TextFieldFocusChanged", "${it.isFocused} : ${it.hasFocus}")
}
I have found this Google issue tracker that seems related to such behavior, though the issue is not exactly related to an initial pass of a composition.
This thing can be solved by specifying some boolean flag depending on your needs, but handling it this way slowly introduces complex boolean evaluations depending on your use-case. Is there any way I can configure a TextField to prevent onFocus callback on initial pass?
Thank you in advance.

There must be a more efficient way to handle this but the most optimal approach I can do for now is to rely on SideEffect.
#Composable
fun ScreenTextFields() {
//..TextField ... onFocusChanged { onTextFieldFocus(it) }...
//..TextField ... onFocusChanged { onTextFieldFocus(it) }...
//..TextField ... onFocusChanged { onTextFieldFocus(it) }...
//..TextField ... onFocusChanged { onTextFieldFocus(it) }...
SideEffect {
// notify and update a boolean state (e.g onFieldsPostComposition)
}
}
and in a state class.
fun onTextFieldFocus(focusState: FocusState) {
if (onFieldsPostComposition) {
// do intended use-case on actual focus changes
}
}
This way, any initial onFocus callback can be ignored/skip for your intended logic, the only part I'm not sure if onFocusChanged { ... } callback is part of the composition/re-composition, not sure if there's a chance that SideEffect will occur first before onFocus callback, but so far, this approach works on my case..

Related

How to get initial focus in compose

How to get initial keyboard focus on an Android compose app?
My view looks like
Parent { Child { Button} }
I tried implementing it in the Parent composable function....
FocusRequester is not initialized. Here are some possible fixes:
1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
2. Did you forget to add a Modifier.focusRequester() ?
3. Are you attempting to request focus during composition? Focus requests should be made in response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }
The following code woirks like a charm....
fun Modifier.requestInitialFocus() = composed {
val first = remember { FocusRequester() }
LaunchedEffect(first) {
delay(1)
first.requestFocus()
}
focusRequester(first)
}
Original:
This error does not happen when implementing it in the composable function, where the target element is a direct child....
So implementing it in the Child seems to be a solution....

Update UI rendering in React Natives 'focus' Event Listener

I am navigating from different class objects (screens) within my React Native App. Once I navigate to a given screen, I want to execute different functions including Activity Indicators to show that the function is still running.
Therefore is set up a focus Event Listener which is called every time I navigate back to my screen. My current problem is that my Activity Indicators are not updated within the focus Event but directly after, which is not my wanted behavior as I have to wait for my functions to end without any visual indicator.
class Homescreen extends Component {
constructor(props){
super(props};
this.state = {
showIndicator: false;
}
}
componentDidMount(){
this.props.navigation.addListener('focus', this._onFocus);
}
componentWillUnmount(){
this.props.navigation.removeListener('focus', this._onFocus);
}
_onFocus = async () => {
this.setState({showIndicator: true});
asyncFunc();
};
async asyncFunc(){
//calling a function from swift which calls a C function inside DispatchQeue.main.async
//inside my C function I successfully set my ActivityIndicator to false with an Event which calls
//this.setState({showIndicator: false});
}
render(){
console.log(this.state.showIndicator); // correct value is printed while I wait inside the ```focus``` Event to finish, but the UI is not updated.
return(
{this.state.showIndicator && <ActivityIndicator/>}
)
}
}
I receive the correct current status of showIndicatorbut before the focus Event is not finished, my render does not redraw.
To me it seems like the UI won't update until I am out of my focus Event. How can I reach my goal of displaying the ActivityIndicator before the focus Event or how do I call my function right after the focus Event?
I can achieve my wanted behavior by using the blurevent and setting my ActivityIndicator visible while moving to another screen and after moving back I wait until my function is finished and remove the ActivityIndicator but this seems like a bad workaround and also I can see the ActivityIndicator when moving which might be confusing.
You should replace curly braces around this statement {this.state.showIndicator && } and check.
If it doesn't work accordingly then let me know.

why do we wrap Action with an explicit animation instead of wrapping State change in the Reducer

In their series on SwiftUI animation, the PointFree guys recommend we use explicit animations that wrap actions sent to the reducer in order to trigger animations when state is updated.
My question is why we don't instead wrap the actual State change, performed in the Reducer?
If I wrap the State change with an explicit animation, nothing happens. But if I wrap the Action being sent to the Reducer, animations work.
This code below is roughly adapted from https://www.pointfree.co/episodes/ep136-swiftui-animation-the-basics around minute 16.23
//works
TextField(
"search",
text: viewStore.binding(get: \.searchText, send:
TCSearchAction.updateSearch).animation(.easeInOut)
)
If I remove the animation call in the binding above, and add it directly to the state change, animations don't seem to fire.
// doesn't fire animations
static let reducer = Reducer<TCSearchState, TCSearchAction, Void> {state, action, _ in
switch action {
case let .updateSearch(string):
//this does not trigger animations when value is changed
withAnimation(.easeInOut)
{
state.searchText = string
}

ViewModel Live Data observers calling on rotation

In my view model, I have two properties:
private val databaseDao = QuestionDatabase.getDatabase(context).questionDao()
val allQuestions: LiveData<List<Question>> = databaseDao.getAllQuestions()
I have observers set on "allQuestions" in my fragment and I'm noticing the observer is being called when I rotate the device. Even though the View Model is only being created once (can tell via a log statement in init()), the observer methods are still being called.
Why is this? I would think the point is to have persistency in the View Model. Ideally, I want the database questions to be only loaded once, regardless of rotation.
This happens because LiveData is lifecycle aware.
And When you rotate the screen you UI Controller [Activity/Fragment] goes through various lifecycle states and lifecycle callbacks.
And since LiveData is lifecycle aware, it updates the detail accordingly.
I have tried to explain this with following points:
When the UI Controller is offscreen, Live Data performs no updates.
When the UI Controller is back on screen, it gets current data.
(Because of this property you are getting above behavior)
When UI controller is destroyed, it performs cleanup on its own.
When new UI Controller starts observing live data, it gets current data.
add this check inside observer
if(lifecycle.currentState == Lifecycle.State.RESUMED){
//code
}
I have the same issue, after reading the jetpack guideline doc, I solve it. Just like what #SVK mentioned, after the rotation of the screen, activity/fragment were re-created.
Base on the solution https://stackoverflow.com/a/64062616,
class SingleLiveEvent<T> : MutableLiveData<T>() {
val TAG: String = "SingleLiveEvent"
private val mPending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer<T> { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
override fun observeForever(observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observeForever { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
#MainThread
override fun setValue(#Nullable t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}

Loop activated on mouse movement

I'm just going to explain the context so it is clearer.
I made this menu : my menu
I am looking to make an improved and more advanced version of the same menu.
I made an animation of waves on the cofee's surface and am looking to make it loop when the mouse is moving and to stop looping when it's not.
Sorry for the lack of specifications as I am quite new to actionscript, but I hope somebody will be able to help me. :)
Thanks,
Mathieu
Well, you said it - leverage MouseEvent.MOUSE_MOVE to set a conditional in your looping routine.
private var _isMoving:Boolean = false;
stage.addEventListener(MouseEvent.MOUSE_MOVE, checkMouse);
this.addEventListener(Event.ENTER_FRAME, doLoop);
private function checkMouse(e:MouseEvent):void
{
_isMoving = true;
}
private function doLoop(e:Event):void
{
trace("moving =" + _isMoving);
if(_isMoving)
{
// loop animation
}
_isMoving = false;
}
depending on how you want it to work I would do this as follows:
create an animation of wavy coffee
ensure the animation loops
note that clips loop by default, so all you have to do is match the first and last frames!
place the clip at the edge of your current coffee graphic
double click on the graphic to edit it
drag an instance of the looping animation from the library onto the "edge" of the graphic
OR just replace your entire light brown graphic with an animated one that loops
when the mouse is moving, call play on the animated loop clip
when the mouse stops, call stop on the animated loop clip
Some example code would be along the lines of:
public function init():void {
menuClip.addEventListener(MouseEvent.MOUSE_OVER, onMenuRollOver);
menuClip.addEventListener(MouseEvent.MOUSE_OUT, onMenuRollOut);
}
public function onMenuRollOver(event:MouseEvent):void {
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMove);
/* do the stuff you're currently doing to animate the clip here.
something like: coffee graphic height = ease to mouseHeight */
}
public function onMenuRollOut(event:MouseEvent):void {
/* do the stuff you're currently doing to stop the clip here. */
stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMove);
coffeeClip.stop();
}
public function onMove(event:MouseEvent):void {
resetTimer();
coffeeClip.play(); //note: play has no effect when movie is playing (that's ideal in this case)
}
public function resetTimer():void {
if(mouseMovementTimer == null) createTimer();
mouseMovementTimer.reset();
mouseMovementTimer.start();
}
public function createTimer():Timer {
mouseMovementTimer = new Timer(DELAY, 1); //fiddle with the delay variable. Try 500, at first
mouseMovementTimer.addEventListener(TimerEvent.TIMER, stopAnimationLoop);
}
public function stopAnimationLoop(event:TimerEvent):void {
mouseMovementTimer.removeEventListener(TimerEvent.TIMER, stopAnimationLoop); //optional but recommended
mouseMovementTimer = null;
coffeClip.stop();
}
Of course, you would need to do things like call init() and import flash.utils.Timer and initialize variables like mouseMovementTimer, menuClip, coffeeClip and DELAY.
Warning: This code is off the top of my head and untested. So there's likely to be small bugs in it but you should get the general idea:
add a mouse listener when the user mouses over the menu
remove that listener if the user mouses out of the menu
have that listener play the looping movie clip
trigger an event that will stop the looping clip if movement hasn't been detected in a while
once the trigger goes of, stop the clip
The key is in detecting when the mouse stops moving. Flash detects interaction well but does not detect NON-INTERACTION for obvious reasons. One easy way to solve that is to trigger a timer that will go off once too much time has elapsed since the last activity. Then, when the timer triggers, you know action has stopped!
I think that's the key piece to solving your problem. I hope that helps someone in some way.
~gmale

Resources