EnableBackButton does not work unless AddButton is used

I think there is a bug with the roMessageDialog object. Specifically, I cannot get the back button event unless I also add a button to the dialog.

I do not want to have any button on the dialog, but I do want the user to be able to press back to quit the dialog. However, in the below code, the only way I can get the event is if I include a dummy button. Can anyone confirm they see the same? Can Roku confirm this is a bug?

Function testBackButton() As Void

port = CreateObject(“roMessagePort”)
dialog = CreateObject(“roMessageDialog”)
dialog.SetMessagePort(port)
dialog.SetText(“Testing Back Button”)
dialog.EnableBackButton(true)
dialog.AddButton(1, “Go Back”) 'This line must be present otherwise back button event never occurs
dialog.Show()

while true
dlgMsg = wait(0, port)
if type(dlgMsg) = “roMessageDialogEvent”
Dbg(“EVENT RECEIVED”) 'Never gets executed unless AddButton is included above
if dlgMsg.isScreenClosed() or dlgMsg.isButtonPressed()
exit while
end if
end if
end while

End Function

If you don’t want buttons, then you might want the roOneLineDialog, but I don’t think it supports the back button, so moot point.

Yes, kind of a moot point. I want a title and text in the dialog, but no on screen buttons, so that is why roMessageDialog is a good fit. I didn’t include the text in my example because it does not impact the resulting behavior either way.

I don’t understand why the remote control back button event has any dependency at all on the presence of on-screen buttons.

Do you see the same?

I have a Roku 2.

I should probably describe the use case here a bit more. When the user selects a specific poster, I need to get a lot of metadata from the web before I can proceed to building the next screen. Sometimes, that can take a long time (depending on network and server conditions). So I present a “Please wait” dialog while I am getting the info.

Right now, the user is “stuck” on this dialog screen with no way to back out. I want to give them a way to cancel the request and go back to where they were.

Is there a Roku “best practice” or example around this, given that the back button does not seem to work?

Why not just put a “cancel” button on the dialog? That seems more discoverable by the user than Back. Especially since some users don’t have a Back button on their remote.

–Mark

I think that is the route I have to go. Is there any sample code that demonstrates how to kick off some web requests, while listening for an event (whether that is a back button or on screen button event), and canceling the web request process to go back a screen if one of those events happen?

I am having trouble killing the web request process when I get the event.

“roquoonewbie” wrote:
I think that is the route I have to go. Is there any sample code that demonstrates how to kick off some web requests, while listening for an event (whether that is a back button or on screen button event), and canceling the web request process to go back a screen if one of those events happen?

I am having trouble killing the web request process when I get the event.
I think you want to look at roUrlTransfer.AsyncGetToString and roUrlTransfer.AsyncCancel. Listen on the same port as your message dialog, and you should be good to go. Take a look at the http_get_to_string_with_timeout function in the urlUtils.brs file in the videoplayer SDK app for an example.

Here is an example of what I mean. I want to kick off the function called “doStuffThatTakesAWhile” while I show a “Please Wait” dialog. If the user cancels the request before the “stuff that takes a while” completes, I want it to stop executing that code. But when I set it up as shown below, the output I print shows that doStuffThatTakesAWhile() completes all of its logic before the event handler even gets the message that the customer clicked cancel. The odd thing is that the dialog does in fact close on the screen when I press cancel…long before the line is logged that “User canceled out of dialog”.

Output:

------ Running ------
Got to beginning of doStuffThatTakesAWhile Function
This line should not get executed if user presses back in less than 10 seconds!
User canceled out of dialog


Function testBackButton() As Void

    port = CreateObject("roMessagePort")
    dialog = CreateObject("roMessageDialog")
    dialog.SetMessagePort(port)
    dialog.SetText("Please Wait")
    dialog.EnableBackButton(true)
    dialog.AddButton(1, "Go Back")
    dialog.Show()

	while true
		dlgMsg = wait(10, port)
		if type(dlgMsg) = "roMessageDialogEvent" then
			if dlgMsg.isScreenClosed() or dlgMsg.isButtonPressed()
				Dbg("User canceled out of dialog")
				exit while
			end if
		else if dlgMsg = invalid then
			doStuffThatTakesAWhile()	
		endif
    end while

End Function

Function doStuffThatTakesAWhile() as Void
	Print("Got to beginning of doStuffThatTakesAWhile Function")
	sleep(10000)
	Print("This line should not get executed if user presses back or cancel in less than 10 seconds!")
End Function

Don’t make your “DoStuff” function separate from your dialog function. instead, integrate the “stuff” with the loop that waits for input.

  • Joel

Why is that necessary? And how would it help? Besides being completely impractical (because the “stuff that takes a while” in my actual code calls all kinds of re-usable HTTP, URL, and XML functions), I don’t see how that would change the result at all either way.

Confirmed…same result if I move the 3 lines of sample code into the event loop.

Output:

------ Running ------
Got to beginning of doStuffThatTakesAWhile Function
This line should not get executed if user presses back in less than 10 seconds!
User canceled out of dialog


	while true
		dlgMsg = wait(10, port)
		if type(dlgMsg) = "roMessageDialogEvent" then
			if dlgMsg.isScreenClosed() or dlgMsg.isButtonPressed()
				Dbg("User canceled out of dialog")
				exit while
			end if
		else if dlgMsg = invalid then
			Print("Got to beginning of doStuffThatTakesAWhile Function")
			sleep(10000)
			Print("This line should not get executed if user presses back in less than 10 seconds!")	
		endif
    end while

You’re basically giving the user 10ms to press the back button. You need to use an roTimespan object and check every 10ms if 10 seconds have passed. Each time through the loop you have to do a part/i] of the stuff that takes a while. If your stuff can’t be broken up, then you don’t have much choice but to do it all and put up a “please wait” message. I’ll leave the details to the interested reader.

-JT

So you are saying there is no way at all in a Roku channel to present a “Please wait” message which gives the user an option of canceling the operation they are currently waiting for?

The operation could be as simple as one HTTP request which may take a long time (if the network is slow, for instance). It’s the equivalent of canceling a browser navigation to a new page after it takes longer than the user would like to wait.

It seems hard to believe that Roku devs did not provide some way to listen for a user-driven event and completely (and instantly) close out of a screen when that event arrives.

I don’t think that’s what he’s saying at all. You just have to get a little creative. This is how I’d do it…

WARNING: psuedo-code


	' Create message port that everything will use
	messagePort = CreateObject("roMessagePort")

	' Create url transfer for first URL
	download1 = CreateObject("roUrlTransfer")
	download1.SetPort(messagePort)
	download1.SetUrl(url1)

	 Create url transfer for second URL
	download2 = CreateObject("roUrlTransfer")
	download2.SetPort(messagePort)
	download2.SetUrl(url2)

	' Create and show wait dialog
	waitDialog = CreateObject("roMessageDialog")
	waitDialog.SetMessagePort(messagePort)
	' Add your waitDialog button(s)
	' Show waitDialog
	waitDialog.Show()
	
	' Start downloads
	download1.AsyncGetToString()
	download2.AsyncGetToString()
	
	' Listen for message
	While True
		msg = wait(10, messagePort)
		If msg <> invalid Then
			If Type(msg) = "roUrlEvent" Then
				' Got a URL event, figure out which download it's for and process it
				If msg.GetSourceIdentity() = download1.GetIdentity() Then
					download1Result = msg.GetString()
					' Do download1 processing
				Else If msg.GetSourceIdentity() = download2.GetIdentity() Then
					download2Result = msg.GetString()
					' Do download2 processing
				End If
			Else If Type(msg) = "roMessageDialogEvent") Then
				' Got a message dialog event, probably cancel
				If msg.IsButtonPressed() Then
					If msg.GetIndex() = cancelButton Then
						' Kill the active downloads, and close the wait dialog
						download1.AsyncCancel()
						download2.AsyncCancel()
						waitDialog.Close()
						Exit While
					End If
				End If
			End If
		End If
	End While

Thank you both very much. I was able to use your proposed method such that I can now give the user the ability to cancel the async HTTP request by pressing back or clicking on a dialog “cancel” button (thought it is still a mystery why the back button will not fire an event unless there is also a dialog button).

However, as best as I can tell, your proposed method will only work for Async HTTP calls, correct?

In other words, if I have a function that “does a lot of stuff” which takes a while (but not necessarily makes HTTP calls), is there a way to set it up to run asyncronously (like the built in AsyncGetToString() method), so that I can cancel it via something similar to the HTTP AsyncCancel() ?

Is it possible to define our own Async functions? Or are we relegated to the few Async functions that are provided by the Roku API?

“roquoonewbie” wrote:

In other words, if I have a function that “does a lot of stuff” which takes a while (but not necessarily makes HTTP calls), is there a way to set it up to run asyncronously (like the built in AsyncGetToString() method), so that I can cancel it via something similar to the HTTP AsyncCancel() ?

Is it possible to define our own Async functions? Or are we relegated to the few Async functions that are provided by the Roku API?
Brightscript is not multi-threaded at the moment, except in a few instances where there are Async functions, which allow you to initiate an action, and then perform other actions while waiting for that first action to complete. You are not able to define your own Async functions at the present time.

  • Joel

Thank you for the information, Joel. So, just to confirm my understanding 100%, is there currently no way at all in Brightscript to present a “waiting” dialog message to users while waiting for time-intensive logic to complete, and give the user an option to cancel (or go back) such that the logic which was running is cancelled/aborted? No way at all?

Also, can you comment on why the EnableBackButton property does not work unless AddButton is also used? Is that intended or is it a bug?

I haven’t tested that myself, but if it is behaving as described, it might be considered a bug.

There is no reason why, within any logic I can imagine, you cannot simply check a message port every so often to see if a button has been pressed, and if you are using multiple functions within your logic, then pass the port and dialog box variables from function to function so that it is available in each function. Then if a button has been pressed, you return from wherever you are to the calling function. You may need to use a return value to notify the calling functions that you are aborting your logic.

You could also use global variables to make your dialog and port available everywhere, instead of the more tedious parameter passing.

  • Joel

I thought Brightscript did not support global variables?

From the Brightscript reference manual:

“BrightScript does not support global variables. Except, there is one hard-coded global variable “global” that is an interface to the global BrightScript Component. The global component contains all global library functions.”

Also, how exactly do you check a message port for the last message? Is a “wait()” required? In other words, would this simply return the last message which the port received, or is there a better way?

dlgMsg = wait(1, port)

you would use the m variable to create your port and dialog:

m.port=createobject("romessageport")
m.dialog=CreateObject("roMessageDialog")

“roquoonewbie” wrote:

Also, how exactly do you check a message port for the last message? Is a “wait()” required? In other words, would this simply return the last message which the port received, or is there a better way?
There is a way to do it without using wait:


msg = port.GetMessage()
if type(msg) = "roUniversalControlEvent" then
<act on message>
end if

but wait is a pretty simple way to go. I would use the above method for a game that has high performance demands on your device, otherwise, msg=wait(25,port) would be my suggestion.

  • Joel

Thanks to all of you. Passing the handle to the dialog port down into the time intensive function, and querying the last message posted to it on each loop inside that function worked perfectly.

All it took was this:


msg = dialogport.GetMessage()
if type(msg) = "roMessageDialogEvent" then 
     if msg.IsButtonPressed() or msg.isScreenClosed() then
          return 3
     end if
end if

The simplest solutions are always the best ones. I had no idea you could poll the port for it’s latest message (as opposed to listening to events from it).

Thanks again!