Problems with Services and Web Access

JMB

Active Member
Licensed User
Longtime User
Hi there.

Looking for some help from the experts.

Have been playing around with Services and the data download stuff as created by Erel, and after having made lots of progress learning about this Android and B4A stuff, I have come up against a problem.

My service – WebAccessService – is responsible for calling a specified website, downloading the data in a JSON Map, then saving the data into a local database table using a modified version of DBUtils. It’s very much based on Erel’s code to access a site which runs a PHP script against a MySQL database, and get the result as a JSON data structure.

The main activity has a loop which loops through a list of tables, and uses the table names to set up WebAccessService before starting the service to allow it to download the data for the specified table. This is supposed to allow the user to switch out of the program and the downloads will continue in the background. This works fine.

The mods to DBUtils that I have done allow a callback from DBUtils so that I can monitor the progress of the download using a progress bar.

Initially, the index into the list of tables was controlled by a variable which increments when I press a button view on my activity.

This was so that I could see what was happening as I developed the code. It works fine like this – I press my Download button and the program iterates through each table and shows me a progress bar on the ScrollView UI for each table as it does so. I even have a “Downloaded x of y” thing going on, so the user is not left wondering what is going on.

However, I want ALL the tables to be iterated through automatically one by one rather than having to press a button. So, to that end I changed the button press code into a for/next loop, and put a

DO WHIILE WebAccessService.JOB_STATUS=WORKING
DoEvents
LOOP

into the for/next loop, so that each call to StartService(WebAccessService) would get a chance to complete and update the UI before moving to the next table.

Here’s the problem. As a result of using the FOR/NEXT loop and the DO WHILE LOOP, the WebAccessService inside the for/next loop no longer even gets created! The program gets stuck in the DO WHILE LOOP, but through various LOG calls, I can see that the call to StartService(WebAccessService) does not appear to be working.

If I switch my code back to use the manually incremented index, it works fine! The scroll bars work, everything works, calls to the WebService work - all fine.

If I put the loop back in - the WebService does not get a look-in.

A shortened version of the main loop is shown below. The WebAccessService is almost identical to the Services example that Erel has posted already.

This code won't work as it stands - it just gives the bare bones as I've ripped big chunks of UI stuff out.

Any thoughts as to how I can make my main loop wait until the service sends back JOB_DONE before moving to the next table would be gratefully received.

JMB

*********************************************************

Sub Process_Globals
Dim NumberOfTables As Int :NumberOfTables=22
End Sub

Sub Globals
Dim ButtonStartDownLoad As Button
Dim scvMain As ScrollView
Dim Counter As Int :Counter = 0

[UI VARIABLES]

End Sub

Sub Activity_Create(FirstTime As Boolean)

[SET UP UI ETC ETC – this all works]

FillScrollView ‘this is the View which has a list of progress bars
‘for each table
End Sub

‘Here’s the button which manually increments the counter for the tables

Sub ButtonStartDownLoad_Click
ButtonStartDownLoad.Enabled=False

Do_Download

Counter= Counter +1

If Counter =22 Then
Counter =0
End If
End Sub

Sub Do_Download

Dim Panel1 As Panel
Dim View1 As View

Dim i As Int
‘i= Counter ‘i is the index into the list of tables

For i=0 To NumberOfTables-1

[SOME MORE UI STUFF TO POINT TO THE RIGHT UI – WORKS OK]


'Here's where we set up the call to WebAccessService to get the table stuff
ProgressDialogShow("Communicating with Server...")

[make the desired table – this DROPS it first, then creates it]

CoreDataAccess.Create_Table(Main.SQL1,i)
CoreDataAccess.TableName_For_RequestID(i).TableName)

[SET UP FOR THE WEB ACCESS SERVICE]

WebAccessService.URL="***.php?reqtype=GET_ALL_DATA&reqvar=" & CoreDataAccess.TableName_For_RequestID(i).TableName

WebAccessService.FileTarget="page.html"

WebAccessService.RequestID=i
WebAccessService.SQLHandler=SQL1

[THE CALL!]
StartService(WebAccessService)

[HERE’s the loop that kills it]

Do While WebAccessService.JobStatus=WebAccessService.STATUS_WORKING
DoEvents
Loop
Next
End Sub


Sub FinishDownload
ProgressDialogHide
If WebAccessService.DoneSuccessfully Then
If WebAccessService.Callback_TableIsEmpty = False Then
[update ui]
Else
[update ui]
End If
End If
ButtonStartDownLoad.Enabled=True
WebAccessService.JobStatus=WebAccessService.STATUS_NONE
End Sub

Sub Activity_Resume
If ButtonStartDownLoad.Enabled=False AND WebAccessService.JobStatus=WebAccessService.STATUS_DONE Then
FinishDownload
End If
End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub FillScrollView
[Sets up the ui - WORKS]
End Sub
 

TomK

Member
Licensed User
Longtime User
Hmm based on what you've said, it sounds like using StartServiceAt is probably more efficient than using a DoEvents or the StartService.

I think if I were doing it, I would opt for a global flag, whereby the service itself knows the StartServiceAt time that it should restart (in your case iterate the next "loop")

I'm not sure if that helps, but it's just a thought.
 
Upvote 0

JMB

Active Member
Licensed User
Longtime User
More weirdness

Hi Tom,

Thanks for your response.

What I can't figure out is why when I introduce the loop into the whole thing, the service does not even get created.

What happens is that the system eventually sees that the program has pretty much got itself stuck, and you have to force-close it.

In fact, I actually call that Activity from ANOTHER activity which just has a single button on it to activate the web activity.

What's really weird, is once the force-close has done its thing, the program goes BACK to the higher level Activity. And now, if you press the button to go into the web program, the Service DOES get created (I can tell from log messages), but it crashes because it's got no proper set-up data!

Weird!

JMB
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
B4X:
Do While WebAccessService.JobStatus=WebAccessService.STATUS _WORKING
  DoEvents
  Loop
Next
This is never a good thing to do in an event driven environment, in this case it is hogging the main thread and not letting the Service run. You should never have do nothing loops but should let that Sub exit and use another mechanism, such as a Timer, to resume execution. Your Service could check whether the Activity is paused and save the data, or if the Activity is running could Callsub into it. The Activity when it resumes could check for any saved data.

Look at the section "Accessing other modules" here Service Modules.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
This is never a good thing to do in an event driven environment, in this case it is hogging the main thread and not letting the Service run. You should never have do nothing loops but should let that Sub exit and use another mechanism, such as a Timer, to resume execution. Your Service could check whether the Activity is paused and save the data, or if the Activity is running could Callsub into it. The Activity when it resumes could check for any saved data.

Look at the section "Accessing other modules" here Service Modules.
I completely agree.

DoEvents is not a replacement for letting the process handle the message queue.
Not all messages can be handled during a DoEvents call.
It should only be used to refresh the UI while inside a loop.
 
Upvote 0

JMB

Active Member
Licensed User
Longtime User
Hi guys thanks for the quick replies

I was aware that I was hogging that thread and that has been bugging me too about this. I will have to have a rethink about how I allow the main thread to sequentially call the service to fill the tables and the ui but then sit and wait till the service completes downloading. I know that that "do while loop" is evil!

JMB
 
Upvote 0

JMB

Active Member
Licensed User
Longtime User
The only thing I can think of is getting the service to do a callsub but that feels wrong. How do you get a main thread to sit and wait for a service to complete without hijacking the thread?
 
Upvote 0

JMB

Active Member
Licensed User
Longtime User
I have just re read agraham's post properly this time and realise that he mentioned using a callsub from the service to the main activity so I will restructure things round that to free up the main thread.

Thanks for your very quick responses!

JMB
 
Upvote 0

JMB

Active Member
Licensed User
Longtime User
FOR/NEXT loop and DO WHILE/LOOP removed, code restructured - works a treat!

Thanks again.

JMB
 
Upvote 0

susu

Well-Known Member
Licensed User
Longtime User
I press my Download button and the program iterates through each table and shows me a progress bar on the ScrollView UI for each table as it does so. I even have a “Downloaded x of y” thing going on, so the user is not left wondering what is going on.

I want to make a download progress bar but I'm stuck, could you please show me how to do it?
 
Upvote 0

JMB

Active Member
Licensed User
Longtime User
Hi Susu

I haven't finished my code, but so far I have done this. I am still a noob at all this stuff, but this might help you on the way.

My code makes use of the Sub InsertMaps from Erel's DB_UTILs code module. I took that out, put it into my Service code, and changed it as follows.

I added a couple of global variables into the Service code module.

Dim TotalNumberOfItems as Int
Dim ItemsSaved as Int


Then, after the start of the loop in the Sub InsertMaps section, (For i1 = 0 To ListOfMaps.Size - 1) I added a CallSub back to the calling activity which would update the UI since Services cannot interface with the UI. I call this a callback as you're "calling back" to the main activity.

So, if your calling Activity was Main, and your service was called DataService, the code in InsertMaps would look something like this:

TotalNumberOfItems = ListOfMaps.Size 'NEW CODE

For i1 = 0 To ListOfMaps.Size - 1) 'Erel's code

ItemsSaved=i1+1
CallSub(Main,"ShowProgressInfo") 'New code - this is the callback
..... 'rest of Erel's code
Next i1


The code you need in your Main Activity is something like this, assuming you're putting up a label and a progress bar.

Sub ShowProgressInfo 'Used by DataService for callback

labelProgress.Text = "Downloaded " & DataService.ItemsSaved & " of " & DataService.TotalNumberOfItems & " items."
progressbar1.progress = (DataService.ItemsSaved / DataService.TotalNumberOfItems)*100
End Sub


Note that you should make sure that the Main Activity has not been paused by using IsPaused(Main) to figure out whether the Main Activity will respond. My code does not show that side of things.

Doing the callback does slow things down a bit especially if you are downloading large numbers of items, but you could put in a counter before the callback to only call back every 7 or 20 or 30 or whatever 'x' number of items - you decide.

What's nice is that at least the user sees that something is going on, and can see the progress of the "download/save" operation while connecting to a web service, either through the phone network or through a wireless connection.

If you use the Service code from Erel, you can get it all working in the background, and when you get it working it truly is a good feeling. It sure was for me when I finally got it working without hogging the main thread as previously discussed!

Hope this helps a little bit, and if the theory is all wrong, I am prepared to see it all shot down in flames - it seems to work for me! :)

JMB
 
Upvote 0

susu

Well-Known Member
Licensed User
Longtime User
Thank you JMB. Now I see the way to do this.
 
Upvote 0
Top