How to always redraw a #Composable? - android-jetpack-compose

I have a composable like here.
#Composable
fun MyBasicTextField() {
val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember{ FocusRequester() }
BasicTextField(
modifier = Modifier
.focusRequester(focusRequester),
keyboardActions = keyboardActions ?: KeyboardActions(onAny = { keyboardController?.hide() }),
)
LaunchedEffect(Unit) {
Log.d(TAG, "focusRequester.requestFocus()")
focusRequester.requestFocus()
}
}
When the screen where this composable is used opens for the first time, always the keyboard is shown (above log message is visible).
Then I leave the app at that state and open another app (opening a link on that screen, which opens the default browser for instance)
Tapping on the BACK button (triangle) leaved the other app (e.g. webbrowser) and comes back to the initial screen: Either with opened Android keyboard on some devices or without any keyboard showing.
I have the feeling that the screen did not notice the missing keyboard (which disappreared while leaving the app for the browser) and thus does not recompose anything?
Can I flag to compose the screen from fresh everytime I re-/compose it?

I'm not sure why you need this, because focusing and showing keyboard every time re-composition happens may be undesirable, what if the user is doing something else on the screen which updates the field, you don't want want to show the keyboard but re-composition happens and you pop it again.
Having said this it's of course doable:
For example the argument passed into the LaunchedEffect determines if during re-composition the block will be run.
So you could try something like this:
#Composable
fun MyBasicTextField() {
val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember{ FocusRequester() }
var launchKey by remember { mutableStateOf(0) }
BasicTextField(
modifier = Modifier
.focusRequester(focusRequester),
keyboardActions = keyboardActions ?: KeyboardActions(onAny = { keyboardController?.hide() }),
)
//passing launchKey instead of Unit
LaunchedEffect(launchKey++) {
Log.d(TAG, "focusRequester.requestFocus()")
focusRequester.requestFocus()
}
}

Related

LottieAnimation not displaying in #Preview

My LottieAnimation is not showing inside of a jetpack compose #Preview fun. It works fine when running on an actual device.
#Preview
#Composable
fun LottiePreview() {
MaterialTheme {
Surface {
Column {
Text(text = "Above lottie")
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.no_receipts))
LottieAnimation(
composition = composition,
)
Text(text = "Below lottie")
}
}
}
}
I've tinkered with different settings and constructors but haven't been able to get the preview to work - I also have been unable to find any documentation or open issues on this. Am I just missing something obvious to get lotties working in previews?
Animations don't work on the static previews. You need to tap on that button with a finger with waves on the tips (right after the label "LottiePrevi..."), so you'll be able to see animations running, and also interact with the composables.

Hide soft Keyboard on Drawer open - Jetpack Compose

I have a TextField and a ModalDrawer in a compose screen. I would like to close the soft keyboard when the user opens the drawer, but I haven't been able to figure out how. There is no onOpened lifecycle event that gets triggered in ModalDrawer afaik.
You can use the confirmStateChange parameter in rememberDrawerState() and call keyboardController.hide() when the drawerValue becomes DrawerValue.open like this:
val keyboardController = LocalSoftwareKeyboardController.current
val state = rememberDrawerState(
initialValue = DrawerValue.Closed,
confirmStateChange = {
if (it == DrawerValue.Open) {
keyboardController?.hide()
}
true
}
)
ModalDrawer(
drawerState = state,
...
) {
...
}

Open a Modal Dialog when issue is Done

I’m just starting with Forge and JIRA apps development, I need to open a ModalDialog when an issue changes its state to “Done” (either by dragging it to the Done column or by changing its status in the issue panel). I don’t know where to start, I tried clonning the jira-issue-activity-ui-kit starter but I don’t get where the modal should open, any ideas? Thanks
This is the code I've tried:
const DONE = 'Done';
export async function run(event, context) {
console.log('Hello World!', event, event.changelog.items);
// if the issue is solved (status changed to Done)
if (event.changelog.items.some(function changedToPreferredStatus(change) {
return statusChangedTo(change, DONE);
})) {
let description = event.issue.fields.summary;
// OPEN MODAL DIALOG HERE
}
}
function statusChangedTo(change, to) {
return change.field === 'status' && change.toString === to;
}

How to hide keyboard in iOS mobile automation using Appium

I am using iOS version of 10.2 and xcode version is 8.3.
Can anyone let me know how to hide the keyboard in iOS mobile automation using Appium?
programming language used: Java.
I tried driver.hideKeyboard(), but it doesn't work for me.
So, I tried with way:
by pressing the button specified key name and way
inspect key coordinate with appium and perform action. Both ways are work for me.
// way 1
driver.findElementByXPath(String.format("//XCUIElementTypeButton[#name='%s']", "Done")).click();
// way 2
TouchAction touchAction = new TouchAction(driver);
touchAction.tap(new PointOption().withCoordinates(345, 343)).perform();
You could use java_client library methods:
driver.findElementByAccessibilityId("Hide keyboard").click();
driver.hideKeyboard(HideKeyboardStrategy.TAP_OUTSIDE);
driver.hideKeyboard(HideKeyboardStrategy.PRESS_KEY, "Done");
I noticed that "Done" is not part of the keyboard group. So I tried to use the name "Done" as my reference to get the element. I tried this on my end and it works.
driver.findElementByName("Done").click();
The "driver" set declared as IOSDriver.
You can use below code snippet to hide keyboard:
driver.getKeyboard().pressKey(Keys.RETURN);
Solution for Python - 2020:
#staticmethod
def hide_keyboard(platform):
"""
Hides the software keyboard on the device.
"""
if platform == "Android":
driver.hide_keyboard()
elif platform == "iOS":
driver.find_element_by_name("Done").click()
i prefer to tap last key on keyboard for iOS instead of hide:
#HowToUseLocators(iOSXCUITAutomation = LocatorGroupStrategy.CHAIN)
#iOSXCUITFindBy(className = "XCUIElementTypeKeyboard")
#iOSXCUITFindBy(className = "XCUIElementTypeButton")
private List<IOSElement> last_iOSKeyboardKey;
#HowToUseLocators(iOSXCUITAutomation = LocatorGroupStrategy.CHAIN)
#iOSXCUITFindBy(className = "XCUIElementTypeKeyboard")
#iOSXCUITFindBy(iOSNsPredicate = "type == 'XCUIElementTypeButton' AND " +
"(name CONTAINS[cd] 'Done' OR name CONTAINS[cd] 'return' " +
"OR name CONTAINS[cd] 'Next' OR name CONTAINS[cd] 'Go')")
private IOSElement last_iOSKeyboardKey_real;
public boolean tapLastKeyboardKey_iOS() {
System.out.println(" tapLastKeyboardKey_iOS()");
boolean bool = false;
setLookTiming(3);
try {
// one way
//bool = tapElement_XCTest(last_iOSKeyboardKey.get(last_iOSKeyboardKey.size()-1));
// slightly faster way
bool = tapElement_XCTest(last_iOSKeyboardKey_real);
} catch (Exception e) {
System.out.println(" tapLastKeyboardKey_iOS(): looks like keyboard closed!");
System.out.println(driver.getPageSource());
}
setDefaultTiming();
return bool;
}
I tried using all of above method. In some case, it doesn't work perfectly. In my way, it will tap on top left of keyboard.
public void hideKeyboard() {
if (isAndroid()) {
driver.hideKeyboard();
} else {
IOSDriver iosDriver = (IOSDriver) driver;
// TODO: Just work for Text Field
// iosDriver.hideKeyboard();
// TODO: Tap outside of Keyboard
IOSElement element = (IOSElement) iosDriver.findElementByClassName("XCUIElementTypeKeyboard");
Point keyboardPoint = element.getLocation();
TouchAction touchAction = new TouchAction(driver);
touchAction.tap(keyboardPoint.getX() + 2, keyboardPoint.getY() - 2).perform();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Since the IOS device keyboard doesn't have any "Done" or "Enter" buttons anymore so we can't use any of the Appium server utility interface like HideKeyboardStrategy.
I basically used the TouchAction class tap method to tap at top of the screen and dismiss the keyboard.
TouchAction touchAction = new TouchAction(driver);
int topY = driver.manage().window().getSize().height / 8;
int pressX = driver.manage().window().getSize().width / 2;
touchAction.tap(new PointOption().withCoordinates(pressX, topY)).perform();
Quick & simple solution :
I always try to tap anywhere on screen, may be on
Static text
Image
after entering to hide keyboard unless I explicitly has requirement of interacting with keyboard. This works pretty well for me. Try it :)

Blank Firefox addon panel page with multiple windows

I've followed MDN's document to create a toggle button addon.
Everything works fine except one problem:
Open a second browser window (cmd+n or ctrl+n) and click on the toggle button there
Click on the toggle button on the original browser window without closing the toggle button on the second window
the toggle button's panel becomes blank with the following error message:
JavaScript error: resource:///modules/WindowsPreviewPerTab.jsm, line 406: NS_ERR
OR_FAILURE: Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIT
askbarTabPreview.invalidate]
// ./lib/main.js
var { ToggleButton } = require("sdk/ui/button/toggle");
var panels = require("sdk/panel");
var self = require("sdk/self");
var buttonIndex = 0;
var lastKnownButtonIndex = 0;
var button = ToggleButton({
id: "button",
label: "my button",
icon: {
"16": "./icon-16.png"
},
onClick: handleChange,
});
var panel = panels.Panel({
contentURL: self.data.url("menu.html"),
onHide: handleHide
});
function handleChange(state) {
if (state.checked) {
panel.show({
position: button
});
}
}
function handleHide() {
button.state('window', {checked: false});
}
function assignButtonIndex() {
return (buttonIndex++).toString();
}
The complete addon is here: https://goo.gl/9N3jle
To reproduce: Extract the zip file and $ cd testButton; cfx run and follow the above steps.
I really hope someone can help me with this. Thank you in advance!
It's a bug; you're not doing anything wrong. It's a racing condition between the windows' focus events, and the panel's event, that prevent somehow the panel's hidden event to be emitted properly.
You can try to mitigate with a workaround the issue until is properly fixed. I added some explanation in the bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1174425#c2 but in short, you can try to add a setTimeout to delay a bit when the panel is shown, in order to avoid the racing condition with the window's focus. Something like:
const { setTimeout } = require("sdk/timers");
/* ... your code here ... */
function handleChange(state) {
if (state.checked) {
setTimeout(() => panel.show({ position: button }), 100);
}
}
I am currently using a workaround where I dynamically create a new Panel every time the user presses the toolbar button.
It is faster than the 100ms workaround and also handles a scenario where the user outright closes one of the browser windows while the panel is open. (The 100ms workaround fails in this case and a blank panel is still displayed.)
It works like this:
let myPanel = null;
const toolbarButton = ToggleButton({
...,
onChange: function (state) {
if (state.checked) {
createPanel();
}
}
});
function createPanel(){
// Prevent memory leaks
if(myPanel){
myPanel.destroy();
}
// Create a new instance of the panel
myPanel = Panel({
...,
onHide: function(){
// Destroy the panel instead of just hiding it.
myPanel.destroy();
}
});
// Display the panel immediately
myPanel.show();
}

Resources