Android Question Problem getting services to do a job and then callback activity with result

aregan

Member
Licensed User
Hi

I'm trying to get used to using services for general async calls back to our server from an B4A app.
The idea is what when a user requests something on an Activity, we kick off a service to perform it and have the service callback the activity on completion.


Based on other threads I've read, I'm trying to implement the following type of solution:

In my main activity I do something like this:


B4X:
Sub Globals
    Dim bLoginSuccess As Boolean
End Sub

Sub btnLogin_Click

    Log("frmLogin(), btnLogin_Click()")

    ProgressDialogShow("Attempting sign in ...")
    CallSubDelayed2(svc_wsh_login, "Login", Me)

    Wait For LoginComplete
    ProgressDialogHide
    If bLoginSuccess Then
        Log("frmLogin(), btnLogin_Click(),bLoginSuccess, ActivityFinish  ")
        Activity.Finish
    Else
        Log("frmLogin(), btnLogin_Click(),bLoginSuccess failed")
    End If
  
  
End Sub

Sub LoginComplete(Result As String) As String

    Log("frmLogin(), LoginComplete(), " & Result)
  
    If Result = "SUCCESS" Then
        bLoginSuccess = True
    Else
        bLoginSuccess = False
    End If
  
    Log(Result)
  
    LoginComplete Result
End Sub


------------------------------------------------------------------

The on my service I do something like this:

B4X:
Sub Process_Globals
    Dim CallbackActivity As Object
End Sub

Sub Login(Callback As Object)
    Log("svc_wsh_login(), Login()")
    CallbackActivity = Callback
End Sub


Sub wsh_ServerLoginResult(Params As List)

    Log("svc_wsh_login(), wsh_ServerLoginResult")
  
    If Params.Get(0) = "FAILED" Then   
        Log("svc_wsh_login(), wsh_ServerLoginResult, Failed")
        ToastMessageShow("Login Failed",True)

        CallSubDelayed2(CallbackActivity, "LoginComplete","FAIL")
    Else   
        Log("svc_wsh_login(), wsh_ServerLoginResult, Success")

        CallSubDelayed2(CallbackActivity, "LoginComplete","SUCCESS")
    End If

    StopService("") 'Stop service as I don't believe its needed anymore
  
End Sub



My problem is that when the CallSubDelayed2 calls the Activity.LoginComplete ... it resumes the activity.btnLogin_Click at the ProgressDialogHide line which is good. But the code in the LoginComplete sub doesn't seem to execute (the logs don't write and the bLoginSuccess is unchanged.

I've noticed too some slightly different results by moving the bLoginSuccess into the ProcessGlobals but not the desired result.

So I've completely confused myself on how this process should be coded to get the desired effect.

The desired effect is:
From an activity call a sub in a service to start an async process and wait for a call back to the activity with some variable passed back with details of the outcome.

Any ideas where I'm going wrong?
 

aregan

Member
Licensed User
Hi

I think I've figured out whats going on with this, however, please feel free to clarify for me or point me in a better direction.

After reviewing one of Erels videos on Resumable subs ... I noticed in an example he mentioned the a log comment saying "I stole the click event".
That got me thinking that the WAIT FOR call may be intercepting the call to the LoginComplete sub, and therefore by design the
LoginComplete code would never be excuted if there is a WAIT FOR in progress.

So I changed the code in the activity to something like this and it behaves as expected.

B4X:
Sub btnLogin_Click As ResumableSub
    Log("frmLogin(), btnLogin_Click()")
    


    ProgressDialogShow("Attempting sign in ...")

    StopService(svc_wsh_login)
    CallSubDelayed2(svc_wsh_login, "Login", Me)
    Wait For LoginComplete(result As String)

    ProgressDialogHide
    
    If (result="SUCCESS" Then
        Activity.Finish
    End If
    
End Sub

Sub LoginComplete(Result As String) As String
End Sub
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
not saying this will solve all your problems, but are you
familiar with erel's tutorial on wait for? it relates to http calls,
but i think it's relevant to your LoginComplete sub.

first, you don't need to declare the sub "as string" since you
never do anything with the returned string.

second, if you wait for LoginComplete( Result as String), you
should be able to access Result directly without having to use
the problematic boolean bLoginSuccess. (although you
could set such a flag after you have successfully handled the
LoginComplete event.) look at how erel handles the wait for.
and if that approach works, don't forget to remove all the code
from LoginComplete and paste it below wait for. in fact, now
that i think of it, due to the wait for, there probably shouldn't
be any code in LoginComplete...

third, for the purposes of understanding the process, have you
tried doing things the old-fashioned way, ie, without a wait for? things
still work that way. some of my older apps (which i haven't
"upgraded" to wait for yet), work fine the original way.

fourth, bLoginSuccess as currently implemented can be a problem,
as you have discovered. if you can implement the wait for as
suggested, you can avoid its use entirely. its current value can be
tough to know as the os moves your activity from one state to
another. technically, declaring it as a global allows it to hold its
value, but knowing what the value is can be problematic.

fifth, i'm unfamiliar with some of what you do in your service.
namely, one does not normally need to find out which activity
has called a service. if an activity runs a service, and the
service raises an event when done, simply referring to Main
does the trick: CallSubDelayed2( Main, "LoginComplete","SUCCESS").
your approach seems to get you back to your activity, so no harm, no foul,
i suppose.

sixth, as far as the service "returning" a value to the activity is concerned,
an int instead of a string is conventional and faster: -1, 0, 1, 2, etc).
"SUCCESS" involves 7 comparisons before a match is made. 0 is standard
for success. if your only 2 choices are "SUCCESS" or "FAIL", then all you
need "S" or "F" (or, preferably, 0 and 1 or -1). just sayin'; i know we're talking
nano-seconds.

the whole thing seems conceptually valid. i think you should try LoginComplete
the old-fashioned way and move up to wait for once you know you're getting
back what you're expecting from the server. the old-fashioned way makes you
think about program flow a little differently, but it's a good feeling to see that
part of the app work as expected. wait for has some nuances
 
Upvote 0

aregan

Member
Licensed User
Thanks drgottjr

Appreciate you taking the time to listen to my ramblings :)

I've made much progress in the last hour. As you've suggested, removing all the code from the LoginComplete sub has simplified things and removed most of the issues I was having. Regarding the "old fashioned" way, I hadn't seen it and went straight into the new way :) I've only been coding in B4A for a few weeks so I'm trying to stick with the newer ways as my go to.

Good call on the using a single byte instead of a string to return the result. I think I put that in more for testing / illustration.

I'm not familar with "erel's tutorial on wait for". I've reviewed the resumable subs video and various other articals. I might have read it but after going through various posts I've lost track of which ones I've read and not read.

I think I'm making progress now though - thanks again
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
this is where you want to start: https://www.b4x.com/android/forum/threads/b4x-okhttputils2-with-wait-for.79345/
the wait for (and code that follows) essentially replaces the code that runs inside the event listener (or callback, if you prefer). but i think one needs an understanding of how things originally went and what wait for is actually doing. anyway, no issue with assuming wait for is the only way. with luck, the referenced link will cause a pop in your brain when you see what erel does with how the result from the server is passed to the wait for. you were basically defeating the purpose of the wait for by allow LoginComplete to function as if there was no wait for. the less said about the bLoginSucess flag, the better.

a return code, rather than a verbose string, is an even older throwback to days when memory and processors were small and slow. but a lot of lower level code still uses the traditional conventions for reporting success or (various degrees of) failure. someone using a library would refer to the library author's documentation for the values returned. when - god forbid - your apps crash, you often see errors like ENOMEM, ENOENT, etc. that's what they are. carry on!
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
"and therefore by design the
LoginComplete code would never be excuted if there is a WAIT FOR in progress."
yep, that's what i was saying. if you kill the wait for and put the processing code back in the logincomplete sub/event, you do things the old/valid way. same result, just a different way of handling the event. when you use the wait for, you move the processing code out of the event sub and put it with the wait for. if you have trouble with a wait for, the old fallback is perfect for making sure you're handling the event. since you don't know (because you're too new) what handling the events the old way entailed, i won't get into it. suffice it to say that it was more in the spirit of event-driven programming, but it made you handle program flow quite differently than you can do with wait for.
 
Upvote 0
Top