I have a application with a screen manager class that is causing me some problems.
The application makes requests to the server to perform searches and allows the user to view results.
The application has worked fine on all OS versions up to 4.5 where we are sudden having
problems viewing a screen under certain circumstances.
It occurs when the user has performed a search and they wait for the results.
While waiting for results, they press the trackball which displays a menu.
This is not needed to display the results, it just happened that the user decided to press it.
When the results come back from the server, the results screen should automatically be displayed. On OS 4.5, the code displays the results screen runs but then the application completely falls over. If the user doesn't press the trackball while waiting, the application works fine.
Looking at the logs, they show no exception being thrown and the only suspect line being
System data:VM:DPNAv=78,p
By adding in some more log lines I have discovered that the code is reaching the
UiApplication.getUiApplication().popScreen(screen);
line in the method hideScreen(Screen screen) but when called from hideCurrentScreen(). By adding in some more debugging I find that the active screen at this point is DefaultMenuScreen (as the menu button has been pressed)
So it seems the problem is that I am trying to pop one of my own screens from the display
stack when the DefaultMenuScreen one is the active one. I repeat that this code did work on OS previous to 4.5. By running the same code on the 8300 with OS 4.2.2 with the debugging statements, I can see that the same thing happens, the active screen is the DefaultScreen but removing my own screen does not cause the whole application to crash.
The one way round this I could see, was to change the hideCurrentScreen() to just remove the active screen but this does not seem like the correct way to do it.
Has anyone else had experience of this? Can anyone tell me why this is happening? What are we meant to do if we cannot remove our screens when a DefaultMenuScreen is the active one?
This occurs in both device and simulator for 8310 and 9700.
The screen manager code is as follows.
public class ScreenManager
{
private Hashtable screens;
private String currentScreenName;
public ScreenManager()
{
screens=new Hashtable();
}
/**
* Description of the Method
*
*#param sCardName Description of Parameter
*/
public boolean showScreen( String sScreenName )
{
boolean bSuccess=false;
if (sScreenName != null && sScreenName.length() > 0 )
{
MainScreen screen=(MainScreen)screens.get(sScreenName);
if (screen!=null)
{
// We have a new screen to display so pop the current screen off the stack
hideCurrentScreen();
// If the screen is on the stack then pop the screens until we get our target screen
// otherwise just push the screen onto the stack.
if (screen.isDisplayed())
{
Screen activeScreen=null;
synchronized(UiApplication.getEventLock())
{
activeScreen=UiApplication.getUiApplication().getActiveScreen();
}
while (screen!=activeScreen && activeScreen!=null)
{
activeScreen=hideScreen(activeScreen);
}
bSuccess=(screen==activeScreen);
}
else
{
synchronized(UiApplication.getEventLock())
{
UiApplication.getUiApplication().pushScreen(screen);
bSuccess=true;
}
}
}
}
if (bSuccess)
{
this.currentScreenName=sScreenName;
}
else
{
Logger.warning("ScreenManager: Failed to display screen ["+ sScreenName +"]");
}
return bSuccess;
}
private Screen hideCurrentScreen()
{
Screen activeScreen=null;
if(currentScreenName!=null)
{
MainScreen screen=(MainScreen)screens.get(currentScreenName);
activeScreen=hideScreen(screen);
}
return activeScreen;
}
private Screen hideScreen(Screen screen)
{
Screen activeScreen=null;
if (screen!=null && screen.isDisplayed())
{
Logger.finest("Hiding Screen ["+currentScreenName+"]");
synchronized(UiApplication.getEventLock())
{
UiApplication.getUiApplication().popScreen(screen);
activeScreen=UiApplication.getUiApplication().getActiveScreen();
}
Logger.finest("Hid Screen ["+currentScreenName+"]");
}
return activeScreen;
}
//Rest of code omitted
}
The only way round this I managed to find was what I mentioned in the question. When I want to remove the current screen. I need to check it is the same as the active screen. If it is not the same then I just remove the active screen until I reach the screen I am looking for. This would only happen if a menu or pop up was displayed. Also, I need to add checks to my custom pop-up code to make sure it does not try and remove a screen that has already been removed.
It seems a bit messy but could not find any other alternatives.
The way we pop screens in our app is to explicitly pop the screen you want off the stack, as opposed to just the top-most screen. This either requires keeping track of which screens you have on the stack, or some code to iterate through the screens on the stack and search for the particular screen you want to pop off.
Related
My app tries to support both portrait and landscape modes.
In portrait mode it has to show the status bar, but not in landscape mode (on iOS).
This is achieved by using 1) theme constant landscapeTitleUiidBool=true and 2) overriding StatusBarLandscape, as suggested here:
Codename One iOS Statusbar on landscape orientation.
I have created a test project with two forms and the following logic:
public void start() {
if (current != null) {
current.show();
return;
}
Form home = new Form("Home", BoxLayout.y());
Button ok = new Button("OK");
ok.addActionListener(e -> {
showOKForm(home);
});
home.add(ok);
home.show();
}
// precondition: Toolbar.setGlobalToolbar(true) set in init(Object)
private void showOKForm(Form home) {
Form f = new Form("OK", BoxLayout.y());
f.add(new Label("Thanks"));
f.getToolbar().setBackCommand("", e -> home.showBack());
f.show();
}
This works as expected - but not always!
Testing the app on an iPhone X device things quickly go wrong if we play around a little with form navigation and rotating the device.
All situations occur: both forms in portrait mode showing the status bar correctly and incorrectly (i.e. the title and back command on top of the status bar) and both forms in landscape mode not showing the status bar correctly (i.e. no status bar) and incorrectly (i.e. status bar space above the title and back command).
See the attached picture with partial screenshots (only the relevant top (left) parts are shown).
Scenarios to achieve this are not reproducible: it depends.
The problems also occur in the simulator.
It appears that sometimes the wrong UIID is used: StatusBar vs StatusBarLandscape. (Edit: in the simulator it shows the correct UIIDs but the dimensions are wrong, i.e. "reversed".)
In my real app (i.e. not the test project) I have reproducible situations where things go wrong, especially in this scenario: 1) open a form, 2) rotate the device, 3) go back to the previous form. I tried overriding showBack() to redo the layout or to revalidate, but nothing helps.
Question: is the above implementation correct and how can this problem be solved?
I am trying to implement a Textbox that can show fractions with GWT.
Therefor I have an Canvas were I can draw what I want and receive KeyEvents and MouseEvents.
But on Ipad (Safarie and Chrome) the software keyboard does not show, so I created an Composite and combined the Canvas with a Textbox witch gets the focus after each key or mouse Event on the Canvas.
But the softkeyboard does not show up every time so I tried a bit and can see, that the Textbox seems to get the focus (it gets a blue boarder) but does not always show the cursor.
This does not happen on my Notebook.
Is there any difference between being focused and showing the cursor?
I tried:
Setting the Cursor position
set the Text of the Textbox.
Any help would be appreciated,
Christoph
public void setFocus(boolean b) {
// if (hasFocus) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
public void execute () {
t.setFocus(b);
}
});
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
public void execute () {
box.setFocus(true);
box.setText("x");
box.setCursorPos(0);
// box.setVisible(false);
// box.setVisible(true);
}
});
// t.setFocus(b);
// box.setFocus(b);
// }
}
The iOS browsers don't allow the focus to be set programmatically unless directly in response to a user interaction (i.e. a touch). I believe the reason is to prevent websites bringing up the virtual keyboard for no reason.
The downside is that it clobbers setFocus() for websites that want to use it for legitimate reasons. You can't call setFocus() in a deferred command because that doesn't count as a direct response to the user interaction.
(To be more precise, you can call setFocus() in a deferred command, but it won't have the desired effect as you found out.)
We're using Appium with iOS Simulator and test functions written in Java.
We have an iOS App with screen 1 containing a UICollection view, and tell Appium to click on one of its elements.
This opens screen 2 (and the scrolling animation takes about 500 ms), which also contains an UICollection view. I want to find out the size of the UICollection view of the second screen with Appium.
The problem is that Appium is too fast and executes the findElements() method directly after the click, which causes it to find the UICollection view of the first screen.
clickOnElementOnFirstScreen();
webDriver.findElements( By.className( "UIACollectionCell" ) ).size();
// is supposed to find the UICollection view on the second screen,
// but actually finds the UICollection view on the first screen
Appium provides several waiting functions. However as far as I can see all of them are intended to be used in this fashion:
"wait until element at location X / with name X becomes visible"
If I try to use these waiting functions, they don't wait at all because they immediately find the UICollection view of the first screen, which has the same location and name as the one on the second screen.
The only solution I have found is to use Thread.sleep:
Thread.sleep(1000);
webDriver.findElements( By.className( "UIACollectionCell" ) ).size();
But we don't want to use Thread.sleep in code that will run on the client's server on hundreds of tests.
We might be able to modify the App and enter metadata into the views so that Appium is able to distinguish them, but this situation occurs in several places and the App is being programmed by the client, so we want to avoid this too.
What is a simple and safe way to wait for the new screen to appear, without modifying the code of the iOS App?
I have found only dirty workaround for this issue.
static waitFor(Duration duration) {
try {
def WebDriverWait wait = new WebDriverWait(mobileDriver, duration.standardSeconds)
wait.until(visibilityOfElementLocated(By.xpath("//Fail")))
//Wait until false case is visible to ensure proper timeout
} catch (Exception e) {
}
}
Another workaround/solution that has been posted on the Appium forums is:
First search for some other element that distinguishes the 2. screen from the 1. screen; once that is visible, it's safe to search for the originally desired element.
I am trying to make my program use less resources when I send it to the background through overriding the onClose() function. My first step is to stop it painting text and gauge fields.
I've been reading this doc on Efficiency,
"Eliminating unnecessary processing on the device"
"You can use methods to stop animating or repainting the screen when the screen is not visible, and resume when the screen is visible again. You can override Screen.onExposed(), which is invoked when your application's screen is on top of the display stack and displayed to the user. You can override Screen.onObscured(), which is invoked when your application's screen is not displayed to the user or is obscured by another screen."
I know if I use isForeground(), it will stop it from painting if my app is in the background, but will it do the same if it is obscured by another app? It seems much more simpler to use isForeground()
private boolean isExposed;
protected void onExposed()
{
isExposed = true;
}
protected void onObscured()
{
isExposed = false;
}
public void batteryStatusChange(int status)
{
// TODO Auto-generated method stub
if ((status & DeviceInfo.BSTAT_LEVEL_CHANGED) != 0)
{
//is there a difference between the two if's?
//if(isExposed)
//if(UiApplication.getUiApplication().isForeground())
{
batteryStatusField.setText(getBatteryLevel());
bitGauge6.setValue(DeviceInfo.getBatteryLevel());
}
}
}
public boolean onClose()
{
UiApplication.getUiApplication().requestBackground();
return true;
}
In general, isForeground() tells you if your app is running in the foreground. However, as you've implemented it, the isExposed variable only tells you when the screen that it belongs to has been exposed, or obscured (because you're setting it in the Screen.onExposed() and Screen.onObscured() methods).
For your app, maybe you only have one Screen subclass?
Most apps will have many screens. So, in that situation, the isExposed variable would only be telling you whether or not one (of many) screens is showing.
If your app only has the single Screen, then either technique should work for you.
However, as I said in my comment, if the code you've posted is showing all that you're doing in the batteryStatusChange() callback, then I don't think you really need to worry about performance. It's good to be considerate of performance on mobile devices, but neither of the UI calls you make in that method should incur any significant performance cost.
batteryStatusField.setText(getBatteryLevel());
bitGauge6.setValue(DeviceInfo.getBatteryLevel());
Is anyone able to actually make it work properly in Flex SDK 4.6?
Here's a short snippet :
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
addedToStage="onAddedToStage(event)"
title="Title">
<fx:Script>
<![CDATA[
private function onAddedToStage(event:Event):void {
if (stage.autoOrients) {
stage.addEventListener(StageOrientationEvent.ORIENTATION_CHANGING, orientationChanging, false, 0, true);
}
}
private function orientationChanging(event:StageOrientationEvent):void {
if (event.afterOrientation == StageOrientation.DEFAULT || event.afterOrientation == StageOrientation.UPSIDE_DOWN) {
event.preventDefault();
}
}
]]>
</fx:Script>
</s:View>
What I'm trying to achieve is to support Landscape mode in both orientations, so if user turns the device 180 degress, the screen should also rotate. But there should be no action at all, when user rotates the device to one of portrait orientations. Instead, I'm seeing width changes to navigator action bar and sometimes content in portrait orientations, so apparently preventing the event is not enough. I'm using the "official" way Adobe suggests, but the problem is that it's not working very well. Granted, the stage does not change, but it seems that there's something firing in navigator anyway, since you can see the action bar width changing.
I had some success with explicitly setting layoutbounds to fixed width in handler method - this prevents changing the actionbar width, but it's only a temporary solution - if the view is a subject to a transition, or some other redraw - it will again render with bad sizes. As if there was something below that was telling it that it's in portrait mode, even though I'm trying to prevent it.
Before you detonate with some silly ideas like "autoOrient = false", don't. It's clearly not a solution for this problem. Obviously it's a bug with Flex SDK - did anyone find a way to fix it or a stable workaround?
EDIT: apparently others bumped into similar issue:
- http://forums.adobe.com/message/3969531 (the main topic is about something else, but read magic robots's comment)
- http://forums.adobe.com/message/4130972
I'm not sure if this is the right one, did I do something wrong in the end, but after a lot of struggle, I've found this one to be stable solution:
private function onAddedToStage(event:Event):void {
if (stage.autoOrients) {
stage.removeEventListener(StageOrientationEvent.ORIENTATION_CHANGING, orientationChanging);
stage.addEventListener(StageOrientationEvent.ORIENTATION_CHANGING, orientationChanging, false, 100, true);
}
}
private function orientationChanging(event:StageOrientationEvent):void {
event.stopImmediatePropagation();
if (event.afterOrientation == StageOrientation.DEFAULT || event.afterOrientation == StageOrientation.UPSIDE_DOWN) {
event.preventDefault();
}
}
First thing to note is that addedToStage fires few times (2-3) in mobile application. I don't know why, there's no addChild in my code, obviously. Maybe the AIR runtime does something else. So, to avoid adding unnecessary amount of handlers, the common technique is to remove handler first - it won't do anything, if such handler is not yet registered, but if there is, it will remove it, which will maintain the handler count on 1.
Second thing is the priority of the event - it won't work on 0, it has to be set on something big, to launch before stuff in AIR runtime.
Last thing - event.stopImmediatePropagation() - now, that we're the first to handle the event, we cant prevent this event to be sent further up in this particular scenario.
This together makes the orientation preventing working perfectly - for me the landscape and the reversed landscape (rotated_left, rotated_right) were working and transitioning, while portrait modes did not affect the view at all.
Now, there's danger here - you might want to remove the listener upon leaving the view (on transition animation end, view deactivate or something), because stopImmediatePropagation will prevent the event to be handled in other parts of your application.
I hope Adobe (or Apache now, actually) will take a closer look at this problem and trace my solution.
EDIT
There remaisn a last issue with this solution, which is if application starts while device is in DEFAULT or UPSIDE_DOWN orientation, in this case application will be in portrait mode.
To fix this, a solution is to change Aspect Ratio within addedToStage handler:
if(Stage.supportsOrientationChange) {
stage.setAspectRatio(StageAspectRatio.LANDSCAPE);
}
So I had the same problem you had. I think I finally figured out the solution. Heres what I did:
<s:TabbedViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
...blahblahblah...
width="1024"/>
protected function tabbedviewnavigatorapplication2_applicationCompleteHandler(event:FlexEvent):void {
stage.autoOrients=true;
preventOrient();
}
private function preventOrient():void {
if (stage.autoOrients) {
stage.removeEventListener(StageOrientationEvent.ORIENTATION_CHANGING, orientationChanging);
stage.addEventListener(StageOrientationEvent.ORIENTATION_CHANGING, orientationChanging, false, 100, true);
}
}
private function orientationChanging(event:StageOrientationEvent):void {
if(event.afterOrientation == StageOrientation.DEFAULT || event.afterOrientation == StageOrientation.UPSIDE_DOWN || event.afterOrientation == StageOrientation.UNKNOWN) {
event.preventDefault();
}
}
Worth noting is that in the application complete handler I set stage.autoOrients to true because in the app.xml file I have it set to false, due to having a splash screen and not wanting users to re-orient the screen during that time. Really the only thing I did different is account for the StageOrientation.UNKNOWN and prevent whatever that would do, set the width to 1024(for the iPad screen, might be different for other tablet devices) in the main mxml file, and removed the stopimmediatepropagation. Hope this helps.