Component Development: Using asynchronous, non-blocking web requests (II/II)
This is part two of this post. The first post tackled asynchronous web requests using the REST library from Embarcadero that is shipped with new versions of Delphi.
Now we will turn our attention to the web request that downloads an image from the web. I will use two components that are part of Delphi as well and can be used mutli-platform without any restrictions.
As mentioned in the first post, it makes sense to declare fields for things that are used to initiate and to work with the response of a request as they are mostly written in different methods and thus also in different declarative scopes, i.e. local variables that were available when iniating the request are no longer available.
FHTTPClient: TNetHTTPClient; FHTTPRequest: TNetHTTPRequest;
These components require the following units to be added to the uses clause – as the documentation lacks this explicit information, I am a bit more detailed than usual:
uses // ... System.Net.URLClient, System.Net.HttpClient, System.Net.HttpClientComponent;
The request is linked to the client. What makes things very confusing is the fact that both the client and the request have a property called “Asynchronous”. The documentation clearly states though that which component you use to initiate the request determines the property being used. As we will use the Request object to initiate the request, we will set that property to True.
// create objets FHTTPClient := TNetHTTPClient.Create(nil); FHTTPRequest := TNetHTTPRequest.Create(nil); // link request to client FHTTPRequest.Client := FHTTPClient; // enable asynchronous requests FHTTPRequest.Asynchronous := true; // set method to be executed after a request completes FHTTPRequest.OnRequestCompleted := OnPictureRequestCompleted;
That also means that these classes follow a different design principle when compared to the REST library classes. We do not have separate methods for synchronous and asynchronous requests. These classes use a property to determine which requests to use instead. If you are a traditionalist when it comes to object-oriented design, the property approach makes more sense as you set the properties of the object and then initiate an action. An action should not use parameters that determine the characteristics of an object. On the other hand you could argue that the object describes a request and not how the data is being requested and thus it should be a parameter on the Get or Put method… Both argumentations make sense and as I said during my lectures multiple times: There is no right or wrong here, there is just a valid exaplanation to support one’s class design.
Finally, we also set the method that is to be executed when the request completes. This is designed as an event and not as a parameter.
procedure TFlxPexelsBase.OnPictureRequestCompleted(const Sender: TObject; const AResponse: IHTTPResponse); begin FResponseData.Clear; FResponseData.LoadFromStream( AResponse.ContentStream ); ProcessDownloadedPicture; // call change events // ... end;
The method does nothing fancy. It reads the reponse data and transfers it into a field variable.
Now comes the tricky part. As you surely remember, the pictures have to be handled differently with regard to the platform they are used. FireMonkey is able to load an image from a stream and can determine the image type. The VCL, however, needs more code. You will find the solution to this problem and the class design I used in previous blog posts.
Thus, the method ProcessDownloadedPicture is declared as abstract in the base class and is implemented in the framework-specific classes. The code is exactly the same code as before. The only difference is that the base class initiates the web request now:
procedure TFlxPexelsBase.RequestUrl(AUrl: String); begin FHTTPRequest.Get( AUrl ); end;
The framework-specific classes are merely responsible to handle the result of the request in the context of their underlying platform. The asynchronous implementation even improves the object-oriented design architecture as we gain a very nice separation of concerns through this approach.
It hurts me to say, but that is all that needs to be done yet again. No complex threads, just simple Delphi methods that are easy to understand and have a very low complexity.
The Pexels component is now ready to be used on a mobile platform. Any application will stay responsive when using the component which is an underestimated requirement in Apple’s iOS that causes a lot of unforeseen crashes – those ‘crashes’ will turn out to be application terminations caused by the operating system intentionally, but are very hard to find.
Looking forward, I have gotten some feedback that the usage of the Embarcadero REST library is still not clear to many developers. Thus, I will blog about the usage of the REST library in conjunction with the Pexels API next.