Posts mit dem Label Seattle werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Seattle werden angezeigt. Alle Posts anzeigen

Dienstag, 23. August 2016

Native Barcode Scanner Integration

1) Introduction

There are lots of solutions, showing how to implement a barcode scanner into your FireMonkey app.

There are different problems with all of these solutions:
  • not platform independent
  • platform dependent API handling
  • using external libraries
  • missing code formats (f.e. no QR-Code support)
  • cost a lot of money
  • confusing or difficult licences
  • expect another app to be installed (BarcodeScanner)
  • [...]
Especially the last problem is quite uncharming, because we can't demand to install another app to use our app.
On Android the Java ZXing library is used, which is not installed by default on the customers device. So the Google Barcode Scanner app need to be installed from PlayStore.

2) Solution

A million thanks to the work of Spelt! He migrated the Java ZXing open-source API to Delphi!
And the best thing about it: It works!


The reason, why I post about external code is, because in my opinion, he didn't got enough credit for his effort.
In these times, where everywhere QR-Codes are used, a barcode scanner is fundamental!

  • It's free!
  • native code for Windows/Android/iOS/OSX
  • Simple and fast
  • Multiple barcodes: QR-Code, Code-128, Code-93, ITF


3) Integration

We want build a mini application with a single form displaying the camera, framed by a darkened area and scanning line.

The form is scanning on the fly when starting the application. 
If a code was recognized, we'd like to stop scanning and showing up the content in a message dialog.

I will not explain how to build a FireMonkey form. Therefor take a look at tons of tutorials all around the internet.
[...]

constructor TMyBarcodeReader.Create();
var lAppEventSvc : IFMXApplicationEventService;
begin
  inherited;

  fCamera := TCameraComponent.Create(nil);
  fCamera.Kind      := FMX.Media.TCameraKind.ckFrontCamera;
  fCamera.FlashMode := FMX.Media.TFlashMode.fmFlashOff;
  fCamera.Quality   := FMX.Media.TVideoCaptureQuality.MediumQuality;
  fCamera.OnSampleBufferReady := doOnCameraSampleBufferReady;

  fFrameTake := 0;

  if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(lAppEventSvc)) then
    lAppEventSvc.SetApplicationEventHandler(AppEvent);

  fScanManager := TScanManager.Create(TBarcodeFormat.Auto, nil);
end;

procedure TMyBarcodeReader.start();
begin
  fResultReceived := false;

  fCamera.Active := false;
  fCamera.Kind   := FMX.Media.TCameraKind.BackCamera;
  fCamera.Active := true;
end;

procedure TMyBarcodeReader.stop();
begin
  fCamera.Active := false;
end;

procedure TMyBarcodeReader.doOnCameraSampleBufferReady(Sender: TObject; const ATime: TMediaTime);
begin
  TThread.Synchronize(TThread.CurrentThread, getImage);
end;

procedure TMyBarcodeReader.getImage();
var lScanBitmap : TBitmap;
    lReadResult : TReadResult;
    lImgObj     : TObject;
    lOutBitmap  : TBitmap;
begin
  // get the TImage where we want to display the camera image
  if assigned(fOnDisplayImage) then fOnDisplayImage(lImgObj)
                               else lImgObj := nil;

  if (not assigned(lImgObj)) or not (lImgObj is TBitmap) then exit;
  lOutBitmap := TBitmap(lImgObj);

  fCamera.SampleBufferToBitmap(lOutBitmap, true);

  if fScanInProgress then
    exit;

  lScanBitmap := TBitmap.Create();
  lOutBitmap.Assign(lScanBitmap);
  TTask.Run(
    procedure
    begin
      try
        if not fResultReceived then
        begin
          fScanInProgress := true;
          lReadResult     := fScanManager.Scan(lScanBitmap);
          fScanInProgress := false;
        end;
      except
        on E: Exception do
        begin
          fScanInProgress := false;
          TThread.Synchronize(nil,
            procedure
            begin
              //
            end);

          if assigned(lScanBitmap) then
            freeAndNil(lScanBitmap);

          exit;
        end;

      end;

      TThread.Synchronize(nil,
        procedure
        begin
          if assigned(lReadResult) then
          begin
            fResultReceived := true;
            if assigned(fOnResult) then
              fOnResult(lReadResult.Text);

            stop();
          end;

          if assigned(lScanBitmap) then
            freeAndNil(lScanBitmap);

          freeAndNil(lReadResult);
        end);
    end);

end;

// Make sure the camera is released if you're going away
function TMyBarcodeReader.appEvent(pAppEvent : TApplicationEvent; pContext : TObject) : Boolean;
begin
  case pAppEvent of
    TApplicationEvent.WillBecomeInactive :
      fCamera.Active := false;
    TApplicationEvent.EnteredBackground :
      fCamera.Active := false;
    TApplicationEvent.WillTerminate :
      fCamera.Active := false;
  end;
end;

[...]
This is just an excerpt on how to implement the API. Spelt also got a demo, where you can have a look at.

Hint:

Simply build a Form with an TImage in the back. Add 4 black rectangles with transparency as a darkened frame and a red line, symbolizing the ancient scanning line.
Then include the TMyBarcodeReader component.

Inside TMyBarcodeReader.Create() we create the logical TCameraComponent and the ScanManager. Here the TMyBarcodeReader.doOnCameraSampleBufferReady() event is applied to the TCameraComponent. 
So, every time we get a new camera image, we enter this event.


constructor TMyBarcodeReader.Create();
var lAppEventSvc : IFMXApplicationEventService;
begin
  inherited;

  fCamera := TCameraComponent.Create(nil);
  fCamera.Kind      := FMX.Media.TCameraKind.ckFrontCamera;
  fCamera.FlashMode := FMX.Media.TFlashMode.fmFlashOff;
  fCamera.Quality   := FMX.Media.TVideoCaptureQuality.MediumQuality;
  fCamera.OnSampleBufferReady := doOnCameraSampleBufferReady;

  fFrameTake := 0;

  if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(lAppEventSvc)) then
    lAppEventSvc.SetApplicationEventHandler(AppEvent);

  fScanManager := TScanManager.Create(TBarcodeFormat.Auto, nil);
end;

With TMyBarcodeReader.start() we startup the scanning process, by activating the back camera.


procedure TMyBarcodeReader.start();
begin
  fResultReceived := false;

  fCamera.Active := false;
  fCamera.Kind   := FMX.Media.TCameraKind.BackCamera;
  fCamera.Active := true;
end;

When the camera service provides the current camera image, we automatically enter the TMyBarcodeReader.doOnCameraSampleBufferReady() event, where we synchronize our "getImage" method.

TMyBarcodeReader.getImage() is the main handling routine. At first we want to get the destination image by the "OnDisplayImage" event, to know where to display the
camera image.
  // get the TImage where we want to display the camera image
  if assigned(fOnDisplayImage) then fOnDisplayImage(lImgObj)
                               else lImgObj := nil;

  if (not assigned(lImgObj)) or not (lImgObj is TBitmap) then exit;
  lOutBitmap := TBitmap(lImgObj);
Then we're requesting the TCameraComponent image itself directly onto our form image.
fCamera.SampleBufferToBitmap(lOutBitmap, true);
In the next step we're about to run a TTask where we want to scan the latest camera buffered image.
if not fResultReceived then
begin
  fScanInProgress := true;
  lReadResult     := fScanManager.Scan(lScanBitmap);
  fScanInProgress := false;
end;
If we found a code, we'll stop the scanning process by deactivating camera and entering the "OnResult" event.
if assigned(lReadResult) then
begin
  fResultReceived := true;
  if assigned(fOnResult) then
    fOnResult(lReadResult.Text);

  stop();
end;
The last method you see, is the platform service event. When the app status changes, for example, if it's becoming inactive, we do not need to scan anymore. So we're deactivating the camera component.

function TMyBarcodeReader.appEvent(pAppEvent : TApplicationEvent; pContext : TObject) : Boolean;
begin
  case pAppEvent of
    TApplicationEvent.WillBecomeInactive :
      fCamera.Active := false;
    TApplicationEvent.EnteredBackground :
      fCamera.Active := false;
    TApplicationEvent.WillTerminate :
      fCamera.Active := false;
  end;
end;

4) Conclusion

As always I'd like to thank you for reading my blog. Let me know if there are some bugs, mistakes or problems with this article or component.



(*) These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval by this blog of any of the products, services or opinions of the corporation or organization or individual. This blog bears no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.

Samstag, 30. April 2016

OpenWeatherMap integration

1) Introduction

Some of you may always wanted to show up the current weather at a specific location or connect weather information with your app data.

For example: In case you've got a skiing app, you only want to recommend slopes where the wether and temperature is suitable.



2) The Effort

We want to build an app where we can request weather information by city, longitude/latitude, zipcode and country. We also want to show up a 5 day forecast for a specific city. And as a small bonus, we want to list a number of weather stations nearby a location.
With the first release of our app we want it for free, because we can't estimate the number of users.
The guys from OpenWeatherMap provide some of their data for free under certain conditions.
But for our demo app it supports all features we'll need.

http://openweathermap.org/(*)


The free version got the following restrictions:
  • less equal 60 requests per minute
  • current weather information
  • 5 days / every 3 hour forecast
  • data updates in less then 2 hours
  • API version support: only current version
  • NO 16 days / daily forecast
  • NO SSL
  • NO UV-Index data
  • NO bulk download
When our app works and more and more users join, we could switch to a payment model, to handle an increasing access rate.

3) The Integration

3.1.) Get your account

At first we need to sign up for an account, because we'll need an individual API-Key.
At https://home.openweathermap.org/users/sign_up (*) we can easily register.

Afterwards you'll find the API-Key in your profile information.

3.2.) Download OpenWeatherMapClient component

In the next step you're welcome to download my OpenWeatherMapClient component.
This component is platform independent (Windows 32/64, Android, iOS, MacOS).
It was built with Delphi 10.1 Berlin (should also run with older versions, where anonymous procedures and generics are supported). 
The component works with Firemonkey (FMX) and VCL applications. It uses a REST client for gathering information.
The component was designed to use the free version of OpenWeatherMap API, so only allowed methods are implemented.

DownloadOpenWeatherMap.zip

3.3) Usage of OpenWeatherMap component

Inside the zip-file you can find the API client component files and our firemonkey demo app.
  • OpenWeatherMap.pas: TOpenWeatherMap component
  • OpenWeatherMapClient.pas: Encapsulated TRESTClient with easy API access methods
  • OpenWeatherMapInterfaces.pas: interfaces representing the API structure
  • OpenWeatherMapTypes.pas: Interface-Implementations
  • example/fmx/OpenWeatherMapApp.dproj: demo app
  • example/images/...: external weather images for displaying current weather (seperate licence)

Example: 
procedure TForm1.FormCreate(Sender: TObject);
var lWeather : ICurrentWeather;
begin
  fOpenWeatherMap := TOpenWeatherMap.Create(Self);
  fOpenWeatherMap.client.api_key := '<YOUR-API-KEY-HERE>';
  lWeather := fOpenWeatherMap.client.getCurrentWeather('London', '', '', 'metric', 'en');
end;

The component uses interfaces, so if you're not firm with those, please take a closer look at: http://docwiki.embarcadero.com/RADStudio/Seattle/de/Objekt-Interfaces (*)

The API client was encapsulated inside a TOpenWeatherMap-Component, because we'd like to use the IOpenWeatherMapClient inside an external module.

Every IWeatherObject works as a JSON Data wrapper/helper instance. So we're accessing the JSON data directly. This is quite performant when receiving the complex JSON structure. We do not interprete, instead we're only unmarshaling at first.
The data is interpreted on demand, when using a specific property (also sub-interfaces!).

A disadvantage is: when accessing the same simple property (integer, float, string or boolean) multiple times, the JSON data is requested every time.

But in most cases you're reading/writing property values only once.



4) Conclusion

As always I'd like to thank you for reading my blog. Let me know if there are some bugs, mistakes or problems with this article or component.



(*) These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval by this blog of any of the products, services or opinions of the corporation or organization or individual. This blog bears no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.