Android Tutorial HttpUtils - Android web services are now simple!

Status
Not open for further replies.
OkHttpUtils2 is now available. OkHttpUtils2 is an improved version and is recommended for new projects. You shouldn't use HttpUtils (v1)!

HttpUtils is made of a code module and a service module. These two modules make it very simple to download online resources and upload data.

The advantages of using HttpUtils are:
  • Much simpler than working with HttpClient directly.
  • Handles parallel calls efficiently and correctly (protects from RejectedExecutionException exceptions).
  • Downloads are not affected by the activity life cycle.
Using HttpUtils

A simple example of downloading a page and returning the page as string:
B4X:
Sub Globals
 Dim b4a As String
 b4a = "http://www.b4x.com"
End Sub

Sub Activity_Create (FirstTime As Boolean)
 HttpUtils.CallbackActivity = "Main" 'Current activity name.
 HttpUtils.CallbackJobDoneSub = "JobDone"
 HttpUtils.Download("Job1", b4a)
End Sub

Sub JobDone (Job As String)
 Dim s As String
 If HttpUtils.IsSuccess(b4a) Then
  s = HttpUtils.GetString(b4a)
 End If
End Sub
First we configure the callback subs. Then we call HttpUtils.Download or HttpUtils.DownloadList. These calls submit a job request to HttpUtils.
A job is made of one or more links.
HttpUtils raises two types of events while processing a job. The UrlDone event is raised for each successful download with the downloaded url and the JobDone event is raised when the whole job finishes.
You cannot submit a new job while a current job is running (though a job can contain many links).

We have three ways to access a downloaded resource:
  • HttpUtils.GetString(Url As String) - Returns the resource as string
  • HttpUtils.GetBitmap(Url As String) - Returns the resource as bitmap
  • HttpUtils.GetInputStream(Url As String) - Returns an InputStream which allows you to manually read the downloaded resource.
These three methods should only be called after the job is done or inside the UrlDone event sub (for that specific Url).
After downloading a resource the Url serves as the key for that resource.

Inside JobDone event sub you should call HttpUtils.IsSuccess before accessing any Url as it is possible that some or all of the downloads have failed. This is not necessary in UrlDone event as UrlDone is called for each successful download.

Second example:
In this example we first download an image and set it as the activity background. Then we download another 6 Urls and print the last one as string. The code for this example is attached.
B4X:
Sub Process_Globals
    Dim ImageUrl As String
    ImageUrl = "http://www.b4x.com/android/images/logo2.png"
    Dim Job2Links As List
End Sub

Sub Globals

End Sub

Sub Activity_Create(FirstTime As Boolean)
    HttpUtils.CallbackActivity = "Main"
    HttpUtils.CallbackJobDoneSub = "JobDone"
    HttpUtils.CallbackUrlDoneSub = "UrlDone"
    Job2Links.Initialize
    Job2Links.AddAll(Array As String( _
        "http://www.google.com", "http://www.yahoo.com", _
        "http://www.bing.com", "http://www.cnn.com", _
        "http://www.twitter.com", "http://www.facebook.com"))
  
    HttpUtils.Download("Job1", ImageUrl)
End Sub

Sub Activity_Resume
    'Check whether a job has finished while the activity was paused.
    If HttpUtils.Complete = True Then JobDone(HttpUtils.Job)
End Sub
Sub UrlDone(Url As String)
    Log(Url & " done")
End Sub
Sub JobDone (Job As String)
    Select Job
        Case "Job1"
            If HttpUtils.IsSuccess(ImageUrl) Then
                Dim b As Bitmap
                b = HttpUtils.GetBitmap(ImageUrl)
                Activity.SetBackgroundImage(b)
            End If
            'Start the second job
            HttpUtils.DownloadList("Job2", Job2Links)
        Case "Job2"
            For i = 0 To HttpUtils.Tasks.Size - 1
                link = HttpUtils.Tasks.Get(i)
                Log(link & ": success=" & HttpUtils.IsSuccess(link))
            Next
            If HttpUtils.IsSuccess("http://www.google.com") Then
                Log(HttpUtils.GetString("http://www.google.com"))
            End If
    End Select
    HttpUtils.Complete = False 'Turn off the complete flag so we won't handle it again if the activity is resumed.
End Sub
What happens when the user presses on the Home key during a download?
The download will complete successfully (we are using a service for this).
However your activity will be paused and the UrlDone and JobDone events will not fire.

When our activity is resumed we should check if we missed anything. This is done with this code:
B4X:
Sub Activity_Resume
    'Check whether a job has finished while the activity was paused.
    If HttpUtils.Complete = True Then JobDone(HttpUtils.Job)
End Sub
We are calling JobDone ourselves if needed.
In Sub JobDone we reset the Complete flag so we know that this job was handled.
UrlDone event should be considered a "nice to have" feature. Your code should be prepared to handle the case where some UrlDone events were missed due to the activity being paused.

The FlickrViewer example was rewritten and the attached code uses this module.
In this example we first go to the "main" page. In this page we find 9 links to 9 images. We submit a second job with all these links.

We show each image as soon as it is ready by using the UrlDone event.
In JobDone we check if all Urls were handled. We can miss some of these events if the activity was paused during download.

flickr_viewer1.png



HttpUtils, similar to DBUtils, aims to simplify common tasks that many developers face. The code is available for you. So changes can be made as needed.

Updates:

V1.04 - The service is now destroyed when it is no longer needed and recreated when needed again. This version also fixes a bug that caused the application to crash if the service was started after the process was killed.

V1.02 - PostString, PostBytes and PostFile methods added to HttpUtils.
These methods make it easy to post data to a web service (using http POST method).
The behavior of these methods is similar to Download and DownloadList. JobDone event is raised after the response from the server is read.

The latest version (v1.04) is included in HttpUtilsExample.
 

Attachments

  • FlickrViewer.zip
    9.8 KB · Views: 3,377
  • HttpUtilsExample.zip
    7.8 KB · Views: 5,814
Last edited:

yonson

Active Member
Licensed User
Longtime User
unknownhostexception - solved

very useful module thank you!!

Just wanted to add in a problem I'd had which had a solution mentioned elsewhere on the forum, when running this in the emulator I kept on receiving an 'unknownhostexception' - i.e. the website couldn't be accessed.

Tried all sorts of things but solution was to close down the emulator and re-start it. No idea why but seems to work, so thought I'd add it in.
 

Widget

Well-Known Member
Licensed User
Longtime User
Erel,
Very nice app, well done. It is demos like this that make the B4A language a joy to learn. :sign0060:

The only minor change I'd make to the app is to check to make sure Flickr doesn't return more than 9 images (or ivs.length). So in ImagesJobDone I added:

B4X:
If i<ivs.Length Then
  ivs(ImageIndex + i).Bitmap = HttpUtils.GetBitmap(HttpUtils.SuccessfulUrls.GetKeyAt(i))
  ivs(ImageIndex + i).Gravity = Gravity.FILL
End If

That way if Flickr changes their format, the app still works.

Widget
 

MaxApps

Active Member
Licensed User
Longtime User
How do I get the HttpUtil to work with B4A? Is it a Library?

M.v.h.
Jakob
 

MaxApps

Active Member
Licensed User
Longtime User
Thanks for the VERY fast answer.
I will try that, and see if I can understand how it works - Completely new with B4A.

Kind regards
Jakob
 

Kevin

Well-Known Member
Licensed User
Longtime User
I'm using this module for some of the HTTP requirements in my app.

I got to thinking though..... what happens to the data that is downloaded? If I understand correctly, the returned text is *saved* to the internal storage? Or just in a variable in memory?

Basically I am using HttpUtils.DownloadList("JobName", JobLinks) to 'open' several URLs (these URLs are simply HTTP commands sent to a device to control it). As such, I don't need or want to save the resulting JSON response from the device.

My fear is that after using my app a couple hundred times, there will be a couple hundred little JSON files in the internal storage. :confused:

Could you please explain a little more about what happens to the data returned, or what (if anything) I should do to get rid of it?

One issue I face is that I can't just rely on the URL to identify it because often times the exact same URL is sent repeatedly in the same job.



Also, out of curiosity, if there is a big job in progress (say a couple hundred URLs), is there any way for me to allow the end-user to abort the job? Could I just destroy the service? And if so, how would I do that?


Thanks!
 
Last edited:

Erel

B4X founder
Staff member
Licensed User
Longtime User
Could you please explain a little more about what happens to the data returned, or what (if anything) I should do to get rid of it?
This code is responsible for downloading the data and temporary saving it in TempFolder.
By default TempFolder is the internal cache folder.
B4X:
Sub hc_ResponseSuccess (Response As HttpResponse, TaskId As Int)
   Response.GetAsynchronously("response", File.OpenOutput(TempFolder, TaskId, False), _
      True, TaskId)
End Sub
The file name is the task id value. For each job it starts from 0. You will not get more files than the number of tasks in the largest job. No matter how long it is running.

One issue I face is that I can't just rely on the URL to identify it because often times the exact same URL is sent repeatedly in the same job.
Maybe you can add a "dummy" parameter to the URL: Google
It will have no affect on the actual call.

Also, out of curiosity, if there is a big job in progress (say a couple hundred URLs), is there any way for me to allow the end-user to abort the job? Could I just destroy the service? And if so, how would I do that?
You can set HttpUtilsService.finishTasks to HttpUtils.Tasks.Size.
It will cause the Job to complete when next Url completes. The currently tasks will still be processed and UrlDone events will be raised. But new Urls will not be submitted.
 

MaxApps

Active Member
Licensed User
Longtime User
I am having trouble, understanding....

I want to send an url, that (when send as the url adresse in a browser) returns a .csv file.
What command should I use? (HttpUtils.PostString? PostFile?....)
How can I read the file, when received?

Kind regards
Jmich
 

MaxApps

Active Member
Licensed User
Longtime User
Thanks.... :)

Thats the way to do it. I have tried in so many different ways, that I was starting to stare my self blind at the issue.

Kind regards
Jmich
 

awama

Active Member
Licensed User
Longtime User
Problem with german letters

Hello.

I use HttpUtils 1.04
In this Sub I download a Website into htmx - string and save as text-file.

Sub JobDone (Job As String)
If HttpUtils.IsSuccess(Url) Then
htmx = HttpUtils.GetString(Url)
End If
HttpUtils.Complete = False
File.WriteString(File.DirRootExternal, "html.txt", htmx)
End Sub

The problem is that in htmx-string german letters as "ß, ä,ö,ü " shows as (Diamond with ?) (in hex-editor shows � ) . How can I get the right letters?

Many thanks for a solution.
Walter
 

awama

Active Member
Licensed User
Longtime User
Problem solved

HttpUtils.GetString assumes that the text is encoded in UTF8. You should find the correct encoding and use this code:
B4X:
   Dim tr As TextReader
   tr.Initialize2(HttpUtils.GetInputStream(url), "xxxx")
   Dim s As String
   s = tr.ReadAll

with this Code german letters as "ä ö ü ß " looks good
Dim tr As TextReader
tr.Initialize2(HttpUtils.GetInputStream(url), "8859-1")
Dim s As String
s = tr.ReadAll

Thanks Erel.

p.s. I love B4a
 

Inman

Well-Known Member
Licensed User
Longtime User
v2Gqrl.jpg


I get the above error, but only sometimes. I use httputils to download just 1 xml file. So I use the HttpUtils.Download function. The same url may work fine at one moment but the very next time it may produce the above error.

This is what I got from the log:
B4X:
** Activity (browser) Create, isFirst = false **
Starting Job: Job1
httputils_internalcheckifcanstart (B4A line: 74)
SuccessfulUrls.Initialize
java.lang.NullPointerException
   at com.mysite.browserapp.httputils._internalcheckifcanstart(httputils.java:122)
   at com.mysite.browserapp.httputils._downloadlist(httputils.java:41)
   at com.mysite.browserapp.httputils._download(httputils.java:34)
   at com.mysite.browserapp.browser._activity_create(browser.java:350)
   at java.lang.reflect.Method.invokeNative(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:507)
   at anywheresoftware.b4a.BA.raiseEvent2(BA.java:105)
   at com.mysite.browserapp.browser.afterFirstLayout(browser.java:84)
   at com.mysite.browserapp.browser.access$100(browser.java:16)
   at com.mysite.browserapp.browser$WaitForLayout.run(browser.java:72)
   at android.os.Handler.handleCallback(Handler.java:587)
   at android.os.Handler.dispatchMessage(Handler.java:92)
   at android.os.Looper.loop(Looper.java:130)
   at android.app.ActivityThread.main(ActivityThread.java:3694)
   at java.lang.reflect.Method.invokeNative(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:507)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
   at dalvik.system.NativeStart.main(Native Method)
java.lang.NullPointerException
** Activity (browser) Resume **
** Activity (browser) Pause, UserClosed = false **

Any idea how to fix this?
 

Inman

Well-Known Member
Licensed User
Longtime User
Can you upload your complete project?

Unfortunately it is part of a bigger project, which is also going to be my first paid app. So I cannot post the source code. I will try to create a new project with the activity I am having trouble with, although it will be tough as there are dependencies with other activities.

One thing I forgot to mention is that this particular activity is not invoked via launcher icon. It is defined as the default handler for http requests. So when the user clicks a link on an email, widget, stock browser etc..the link will be received by this activity (if it is set as the default web browser app). Then if the link is a short url (like t.co or bit.ly as you get from twitter) it expands the url using the following service:

http://api.longurl.org/v2/expand?url=mylink

The response is an XML file with the long url, which is downloaded with httputils and then parsed with XML parser. The error occurs some times when the above url is accessed by httputils. I feel this happens mostly when you check multiple urls within a short time. But I click the subsequent links only after I receive the long url for the current link.

Btw I am using version 1.04 of httputils.
 

Inman

Well-Known Member
Licensed User
Longtime User
This error definitely has something to do with the app not being launched using it's main activity. Here is what I noticed.

I install the app, then hit "Done" instead of "Open" after the installation. Then I head straight to my Gmail client, open a mail with a set of links, click on one of them, choose my app from the list to handle the link and I get that error. This always happens if I follow the above mentioned steps.

Now if I open the app at least one time through the launcher activity (which actually loads the main activity and not the browser activity), then get back to Gmail client and click on the link, it always works correctly.

I have a feeling something about this service is not starting correctly when the app gets launched via a defined intent filter and not from launcher.

Erel, sometime back I had an issue with WrapAsIntentChooser not working and you suggested to use a timer instead of Activity_Create and that actually worked back then. This is something similar and sadly timer didn't work this time.
 

Inman

Well-Known Member
Licensed User
Longtime User
Calling HttpUtils.Process_Globals in the browser activity did avoid the error but the whole phone froze. The same thing happened yesterday when I tried to suppress the SuccessfulUrls.Initialize error using Try...Catch. I only need this particular browser activity to handle urls. So I am hoping I don't need to initialize any other module of mine.

But what about HttpUtilsService? Is it possible to manually initialize the Process Globals of this module?
 
Status
Not open for further replies.
Top