PushModalAsync() new window is hidden under previous window

MattFLMattFL Member ✭✭

Xamarin forms on Android.

I'm using PushModalAsync() to open a new window. Most of the time it works fine, but occasionally the new window is created but is not visible, as if it's hidden behind the previous page. The OnAppearing() event for the new page is triggered. If I tap the menu button on my tablet to bring up the list of open applications, then tap on my application to return focus to my app, then suddenly the new window pops into view. Is there a way to force the new window to pop to the top, or force a screen redraw of some kind after PushModalAsync()? This is how I'm opening the new window:

public void OpenMyNewWindow(void)
{
  try
  {
    if (myNewPage == null)
      myNewPage = new MyNewPageType();
    Navigation.PushModalAsync(myNewPage);
  }
  catch (Exception e)
  {
    // handle errors... 
  }
} 

Best Answers

  • MattFLMattFL ✭✭
    edited March 2018 Accepted Answer

    Agreed. The problem call is to InvalidatePlot(), which ends up in:

    OxyPlot.Xamarin.Android.PlotView.InvalidatePlot (System.Boolean updateData)

    And here is the exception info:

    at Java.Interop.JniEnvironment+InstanceMethods.CallNonvirtualVoidMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x00089] in :0
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

    Making sure everything OxyPlot related runs on the UI thread fixed the problem.

Answers

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Any reason in particular all your pages are Modal?
    Do you get the same behavior if you just PushAsync(mypage);

  • MattFLMattFL Member ✭✭

    Thanks for the reply. The pages are modal because the user is not to interact with any other windows until this one is complete. We are driving the user through a specific path of windows, then they must reverse their way out. modal and push/pop work very well for this. As long as the windows are actually shown. ;)

  • JamesLaveryJamesLavery GBBeta, University ✭✭✭✭✭
    edited March 2018
    With the navigation stack which Forms operates, every non tabbed page is modal effectively, as you have to pop it to get back to the previous page.

    So you don't have to use PushModalAsync and can probably just use PushModal.

    Or am I missing something?
  • MattFLMattFL Member ✭✭
    edited March 2018

    I tried PushAsync() and the new window never appears. I tried switching apps and back and nothing I do makes the new window appear.

    Is there any reason that PushModalAsync() shouldn't work reliably?

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    No reason. And no reason that PushAsync() shouldn't be working. They work for a million other developers. Litterally. Clearly there's something else going on with your project. Like maybe you have other code tying up the UI thread so it can't update.

    Maybe start with a new basic "Welcome to Xamarin" app, and just add a few pages and navigation. Nothing else. Nothing fancy. Just to confirm your environment and understanding of navigation. Then you can start debugging your existing app to see what is causing the issue.

  • MattFLMattFL Member ✭✭

    After digging deeper; The new window contains an OxyPlot piechart that is updated on a timer. The window always opens properly, UNTIL the first time the OxyPlot updates, then any other window opened after that point becomes hidden under the main window. If I switch apps and switch back, etc.. then the new window is suddenly visible and from then on everything works properly. Also if I disable the timer so the OxyPlot is drawn but is never updated, then the windows always work properly. So the OxyPlot is doing something that is causing all new windows to become hidden. Is there any way to force a window to the front, maybe a call I could make inside of OnAppearing() that forces the window to be on top?

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    @MattFL said:
    hat is updated on a timer.

    Whiskey Tango Foxtrot? Whatever team member did that that: Beat them.

    So the OxyPlot is doing something that is causing all new windows to become hidden. Is there any way to force a window to the front, maybe a call I could make inside of OnAppearing() that forces the window to be on top?

    Wrong approach. The problem is not a "whatever its doing". And the fix is not to band-aid some force refresh like this is 1995 Windows Forms. Its doing its job. Its just doing it ON THE UI THREAD.

    1) Get that thing off the timer.
    2) Get the logic behind it properly threaded.
    3) Scrub through the rest of your app and find all the other places that developer did the same thing. If they did it in one place there is no doubt in my mind that they are holding up the UI thread in other places as well: You just haven't found them yet.

  • MattFLMattFL Member ✭✭

    Thanks again for your replies. What we have is some low level developers with limited GUI experience trying to make a GUI work. ;) The pie chart shows a combination of time (the slices grow with every second) and the slices change color as events (as reported by 3rd party hardware) occur. So a timer seemed like a natural way to update a chart that changes according to time. In this case, what would be the better way to trigger an update? Think of an empty pie chart representing a stopwatch, it fills in with color as the second hand goes around, and the color that the second hand is painting changes as events are reported over the network. At the end you're left with a pie chart containing 4 or 5 slices of different colors.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    C# is an Object Oriented language. Try to do things in an OOP way, and driven by events not timers.

    So what event can you use as a trigger for updates?

    (as reported by 3rd party hardware) occur

    Aha... There ya go. Only update the chart when you have updates to report come in.

    Think of an empty pie chart representing a stopwatch, it fills in with color as the second hand goes around

    that still doesn't operate in a tight loop of a timer. Timers bad especially in apps like this where you have limited resources. At the very least you run an await delay for 1000 milliseconds, that then raises an event such as "ClockTickedEvent", or better yet a MessageCenter message that subscribers who care about time can subscribe to - now you have an event you can use, that just so happens to occur every 1 second.

    But in your case you don't need to do that. You have events that come in from the vendor. So use those as your triggers. Maybe make note of when the last one came in. If it is any closer than 10 ms (as an example) skip updating the UI: There is no sense beating the UI updates to death faster than the hardware can handle or the user can even see.

    Additionally... You may not even need to do that. Isn't the chart a result of the data? And the data is part of a binded value on a ViewModel, right? If you update the data doesn't the chart update automatically without code needing to micromanage it? I mean you're not trying to manage the UI elements from the ViewModel are you? Its still all using good MVVM patterns from the chart to the data isn't it?

  • MattFLMattFL Member ✭✭
    edited March 2018

    The UI needs to be updated every few seconds (currently every 5 seconds) but the changes reported by the hardware can be several minutes apart. The UI needs to update continually so users know that something is still happening, it can't wait for hardware events or the user will think the hardware has stopped. So when the timer expires and triggers an event, the latest time and hardware data is sent to the view model, which updates the UI. So basically we want the UI to be updated reasonably frequently so the user feels that something is happening. Depending on what the hardware is doing, it may be many minutes between updates so we cannot use hardware events to trigger UI updates.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    The UI needs to be updated every few seconds (currently every 5 seconds) but the changes reported by the hardware can be several minutes apart.

    So you are re-drawing the exact same thing every 5 seconds for updates several minutes apart? In other words you're doing 100 times more processing than you have to?

    The UI needs to update continually so users know that something is still happening, it can't wait for hardware events or the user will think the hardware has stopped.

    Put some other piddly animation on there... Run a real-time clock just so they see the app isn't frozen... Fade a covering BoxView in and out... I can think of 20 different ways to fool the user without actually holding up the UI for real processing of the same data over and over.

    But actually processing 100 times more than you need to is bad - and you're seeing the result of that choice: Poor app performance.

  • MattFLMattFL Member ✭✭

    No the pie slices are growing every 5 seconds, the color being drawn changes as the hardware reports events. In any event, I don't think the issue is the processor or UI thread being over taxed. Once the window is on top, everything is very responsive, and drawing a simple pie chart every 5 seconds isn't exactly heavy lifting.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Your statements contradict themselves. You say the chart is growing every 5 seconds in response to hardware reports. But the hardware reports come in several minutes apart. Which is it?

    Its not a matter of heavy lifting. Is that your timer loop is running on the UI thread. I've said it at least 3 times now. Is threading unfamiliar to you? Are you aware that there is a thread that handles the UI, and that you can AND SHOULD have all the other work take place on their own threads so they don't hold up each other; including holding up the UI?

  • MattFLMattFL Member ✭✭

    We've having a communication problem. The chart is growing every 5 seconds, it changes color as hardware events occur.
    Picture a stopwatch that is painted as the second hand goes around. Every 5 seconds the hand moves painting in the circle as it goes. When a hardware event occurs, the color being painted changes. All data processing is happening on background threads, the only thing happening on the UI thread is updating the UI.

    Now that said; it's not a performance problem. It's a window is not visible problem.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    We're going around in circles. You're not grasping that I'm telling you that your continuously running code is on the UI thread so it is holding up the UI and thus holding up the page from displaying, until you exit and return.

    It may not be performance heavy... but it isn't letting any other work take place on the UI thread. Its like when you talk to that one barista that talks 100 miles an hour. Its not tough to understand, but you can't sneak a word into the conversation. same going on here. That timer is continuous and not letting anyone else get some work in.

    Simple test to confirm: Stop the timer. Sure the chart won't update but this is just a test. If you stop the timer and all your page navigation works you have your confirmation.

  • MattFLMattFL Member ✭✭

    I understand what you're saying, but that's not the problem here. When the problem occurs, I can click back to pop the window with the pie chart from the stack via PopModalAsync(). It closes immediately and returns me to the parent window. Now the timer is stopped and the window with the chart is gone and I'm looking at the parent window. When I try now to open any new window from the parent window using PushModalAsync(), the new window is not visible. The window is created, OnAppearing() is called, but you cannot see the window. I can click multiple buttons on the parent window to start multiple new windows with PushModalAsync() and it appears visually that nothing happens. If I tab away from my app then tab back, presto there are all my windows stacked up, and they all work perfectly. They were just hidden for some reason.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭
    edited March 2018

    Do the test or don't. I'm not going to beg someone to take the advice.
    Just comment out the timer so it never starts up. Then run the app. What's it going to cost you: 5 minutes? Best case for you is you can say "I did it and it didn't make a difference. You're wrong." and shut me up. There is no down side for you by running the test.

    Either way... Best of luck with the project. I'm bowing out of the conversation now. I'm tired of feeling like I'm having to fight with someone that just knows they're right about what's wrong, yet can't figure out what's wrong who asked for help and input but won't run a test to help the person trying to help them. It just shouldn't be a fight to help someone.

  • MattFLMattFL Member ✭✭

    I said above that the problem does not happen when the chart is not updated via the timer. I also just said above that once the problem begins, it persists after the timer is stopped and the window is gone. It's difficult to communicate what I'm seeing through text alone, but I can tell you it is not an overloaded thread issue. Thanks for trying, I appreciate your effort.

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    Once the timer is going and stacking up work faster than it can be performed its there. Stopping the timer doesn't make all that work that is still waiting to finish just go away. Its still there on the heap/stack waiting to be performed.

    Think of it like backed up traffic on the freeway. Even after the accident is cleared the traffic is still there and still stacked up for another hour. You're seeing the code equivalency of that.

    I'd bet that if you had the timer going, then stop it like you describe... then let the app run for another 30-60 minutes... somewhere in there performance would return. So long as you never go back to the page that re-starts that timer.

  • MattFLMattFL Member ✭✭

    After tabbing away and back to my app, everything works fine, including the page with the pie chart. The timer is still running, the pie chart updates fine, buttons are very responsive. Windows open and close just fine. It just takes the one initial tab away and back then suddenly all is well.

  • JohnHardmanJohnHardman GBUniversity mod
    edited March 2018

    @MattFL -

    Caveat - I haven't looked at OxyPlot, but...

    In the Page that shows the chart, why not use OnAppearing() and OnDisappearing() so that when the Page has disappeared (either it's been closed or another one has been opened over the top) it stops updating its UI, and when it reappears it starts updating its UI?

    Obviously, it shouldn't be updating the chart on the UI if the underlying data has not changed, for all the reasons Clint has mentioned, but there is little point in updating a hidden UI either unless the update in OnAppearing is horribly slow. But, if it is horribly slow, continuing the updates in background will cause the problems Clint mentions. Better to only do the updates when something has changed and the page is visible.

    As for the timer - polling is horribly inefficient. Can't you make it event driven based on underlying data having changed (whether receiving that data via TCP/IP or otherwise - I have to admit I haven't read every post above, so not sure where the data is coming from)?

    As an aside - I'd be curious to know what Navigation.ModalStack contains when you hit the problem described.

  • MattFLMattFL Member ✭✭
    edited March 2018

    Thanks for the help guys, I appreciate your efforts. I believe the problem is now fixed. My OxyPlot objects were allocated on the UI thread, but sometimes an exception would be thrown when the timer thread tried to update the OxyPlot objects. Once the exception occurred, any new windows would be hidden under the main window for some reason. The exception was caught and I didn't notice it hidden in the piles and piles of debug output. Now I am explicitly running the OxyPlot code on the UI thread and the problem is fixed.

  • JohnHardmanJohnHardman GBUniversity mod
    edited March 2018

    That's the sort of scenario where I'd want to understand what was happening to be sure that it is fixed.

    From your description, I would assume that something was trying to interact with the UI from a non-UI thread. It should normally be possible to confirm that from the exception's stack trace and the exception content.

  • MattFLMattFL Member ✭✭
    edited March 2018 Accepted Answer

    Agreed. The problem call is to InvalidatePlot(), which ends up in:

    OxyPlot.Xamarin.Android.PlotView.InvalidatePlot (System.Boolean updateData)

    And here is the exception info:

    at Java.Interop.JniEnvironment+InstanceMethods.CallNonvirtualVoidMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x00089] in :0
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

    Making sure everything OxyPlot related runs on the UI thread fixed the problem.

Sign In or Register to comment.