Migrating to QFuture/Async coding patterns in the ArcGIS Maps SDK for Qt

139
0
2 weeks ago
Labels (1)
DonKemlage
Esri Contributor
4 0 139

Introduction: 

In this blog post, we will take an ArcGIS Maps SDK for Qt code snippet that uses the prior asynchronous programming pattern of the Qt/C++ Signal and Slots and show you how to convert it to the new QFuture asynchronous programming pattern. 

Since the Qt Maps 200.2 release, we have added gradual support to modernize various Qt/C++ API types that perform asynchronous tasks. As of the recent 200.4 release, we have added over 190+ new methods that contain the suffix "Async" and return the QFuture Class. 

With a QFuture, you can: continue processing when the task finishes; be notified of errors; and cancel the task. QFuture's modern syntax of:.then(), .onCanceled(), and .onFailed() keeps the code more concise and readable. It also provides an easy way to chain multiple tasks together. A more in-depth discussion with code examples of how to use these operations can be found in the Qt Maps SDK doc Working with QFuture.

 

Defining the problem:

Let's assume that we have the following Qt/C++ code snippet that uses the older pattern of Signals and Slots on a locator task to obtain some geocode results:

 

// Create a new locator task from the URL provided. 
LocatorTask* locatorTask = new LocatorTask( 
    QUrl("https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer"), this); 
  
// Signal/slot that gets geocode results when the locator task completes. 
connect(locatorTask, &LocatorTask::geocodeCompleted, 
        this, [](QUuid, const QList<GeocodeResult>& geocodeResults) 
        { 
            // Only continue if we have results. 
            if (!geocodeResults.isEmpty()) 
            { 
                // Display the results. 
                qDebug() << "Number results found:" << geocodeResults.count(); 
            } 
        }); 

// Signal/slot that catches errors if the locator task cannot complete. 
connect(locatorTask, &LocatorTask::errorOccurred, [](Error e) 
        { 
            // Display the error occurring. 
            qDebug() << e.message(); 
        }); 

// Call the geocode method with the provided QString on the locator task. 
locatorTask->geocode("Pizza shops"); 

 

To modernize this code, the first thing you will want to do is to figure out which Qt Maps SDK methods can be converted to the new "Async" and QFuture programming pattern. The Qt Creator IDE helps us to know which asynchronous methods can be upgraded. If you are using an older version of the Qt Maps SDK binaries, you will want to upgrade to the latest version (which is v200.4 at the time this blog article was written). Once you are using the latest version of the SDK, in Qt Creator choose Build > Rebuild to compile your app and look for any warning messages that have text like:

  • C4996: 'Esri::ArcGISRuntime:xxxxxxx': Use xxxxxxxxxxxAsync() instead 
  • 'xxxxxxxxx' is deprecated

See the following screen shot: 

VER4_QtCreator1.png

In our case, using the code snippet above, the Qt Creator yields the following 4 warning messages: 

  • C4996: 'Esri::ArcGISRuntime::LocatorTask::geocodeCompleted': was declared deprecated 
  • C4996: 'Esri::ArcGISRuntime::LocatorTask::geocode': Use geocodeAsync() instead  
  • 'geocodeCompleted' is deprecated 
  • ' geocode' is deprecated: Use geocodeAsync () instead 

What we can learn from these warning messages is that the variables that are used for LocatorTask::geocodeCompleted and LocatorTask::geocode can be eliminated to take advantage of the new "Async" and QFuture programming pattern. Even though there are 4 warning messages, and we have 2 ArcGIS Maps SDK for Qt types being impacted, we will only use 1 of the new "Async" methods and that is the LocatorTask::geocodeAsync method.

 

The code conversion process:

In the next set of code conversion steps, I will show how make these changes:

 

Code Conversion Step 1

The first step in the conversion is to scroll near the top of your .cpp file and add the following new include statement:

 

#include <QFuture>

 

All of the new "Async" programming methods added require the QFuture include statement.

 

Code Conversion Step 2 

The way I like to approach the coding upgrade is to modify the old asynchronous call to the new "Async" pattern. In this case, we would change the following line(s) of code from: 

 

// Call the geocode method with the provided QString on the locator task. 
locatorTask->geocode("Pizza shops"); 

 

to: 

 

// Call the geocode async method with the provided QString parameter on the locator task. 
locatorTask->geocodeAsync("Pizza shops") 

 

Let’s interrogate this coding change to explain what is happening:  

First, the code comments were modified to reflect the new programming pattern being used.  

Second, the method: geocode becomes geocodeAsync. We are now using the new "Async" version of the method call.

 

Code Conversion Step 3

The next thing I like to do is stub out the "continuation block" to the QFuture by adding the .then() method to the end of geocodeAsync method. This signifies when the geocode task finishes. This is what the syntax looks like: 

 

// QFuture that occurs if we have geocode results. 
.then(this,[](const QList<GeocodeResult>& geocodeResults)  
{  
    // We will add more code here later....  
})

 

You might notice that the QFuture signature for the .then() methods looks similar to what the original LocatorTask::geocodeCompleted signal's argument was. 

The best way to know the exact signatures required for the old and new coding patterns is to look at the ArcGIS Maps SDK for Qt API Reference documentation for:  

 

Code Conversion Step 4

The next thing I like to do is to copy everything that was in the older pattern for the connect slot for the LocatorTask::geocodeCompleted inside of the curly brackets into the curly brackets for the "continuation block" to the QFuture in the .then() method. This is best demonstrated with some screenshots.  

We copy that part of the code that is circled in blue from within the connect signal for the LocatorTask::geocodeCompleted inside of the curly brackets: 

VER4_QtCreator2.png

Into the curly brackets for the "continuation block" to the QFuture in the .then() method (the copied section is circled in blue):  

VER4_QtCreator3.png

 

Code conversion Step 5 

Given that our coding example contains some error handling, we will tackle that next. 

The first thing we need to do is scroll near the top of your .cpp file and add the following new include statement: 

 

#include "ErrorException.h"

 

Now we will stub out the Error Exception via the QFuture's .onFailed() handler to capture any errors that might occur. We do this by chaining the .onFailed() to the end of the geocodeAsync method, like this: 

 

// QFuture that occurs if we have an error geocoding. 
.onFailed(this, [](const ErrorException& e) 
{ 
    // We will add more code here later.... 
}); 

 

NOTE: The Qt Maps provides for ErrorException, but you can also catch a general QException or specify no type to catch all errors. 

 

Code conversion Step 6 

The next coding change we need is to copy everything that was in the older pattern for the connect slot for the LocatorTask::errorOccurred inside of the curly brackets into the curly brackets for the QFuture's .onFailed() handler. This is best demonstrated with some screenshots.  

We copy that part of the code that is circled in blue from within the connect signal for the LocatorTask::errorOccurred inside of the curly brackets: 

VER4_QtCreator4.png

Into the curly brackets for the QFuture's .onFailed() handler (the copied section is circled in blue): 

VER4_QtCreator5.png

 

Code conversion Step 7 

The final step we need to perform is some code clean up. We do this by eliminating the entire code block for the older patterns of the LocatorTask::geocodeCompleted and LocatorTask::errorOccurred connect Signals and Slots. This means we would delete these code sections: 

 

// Signal/slot that gets geocode results when the locator task completes. 
connect(locatorTask, &LocatorTask::geocodeCompleted, 
        this, [](QUuid, const QList<GeocodeResult>& geocodeResults) 
        { 
            // Only continue if we have results. 
            if (!geocodeResults.isEmpty()) 
            { 
                // Display the results. 
                qDebug() << "Number results found:" << geocodeResults.count(); 
            } 
        }); 

// Signal/slot that catches errors if the locator task cannot complete. 
connect(locatorTask, &LocatorTask::errorOccurred, [](Error e) 
        { 
            // Display the error occurring. 
            qDebug() << e.message(); 
        });

 

Your code should now look like this and be able to compile without the deprecation warnings:

 

// Create a new locator task from the URL provided. 
LocatorTask* locatorTask = new LocatorTask( 
    QUrl("https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer"), this); 
  
// Call the geocode async method with the provided QString parameter on the locator task. 
locatorTask->geocodeAsync("Pizza shops") 
       
    // QFuture that occurs if we have geocode results. 
    .then(this,[](const QList<GeocodeResult>& geocodeResults) 
      { 
        // Only continue if we have results. 
        if (!geocodeResults.isEmpty()) 
        { 
          // Display the results. 
          qDebug() << "Number results found:" << geocodeResults.count(); 
        } 
      }) 
       
    // QFuture that occurs if we have an error geocoding.  
    .onFailed(this, [](const ErrorException& e) 
      { 
        // Display the error occurring. 
        qDebug() << e.error().message(); 
       });

 

 

Summary: 

In this blog we have learned about the new QFuture capabilities added to the ArcGIS Maps SDK for Qt. Important hyperlinks were provided to reference materials for the migration from the older coding pattern of Signals and Slots. Finally, we saw an actual code example of the conversion process. I hope this blog is beneficial to your upgrade efforts in using the new “Async” methods available in the ArcGIS Maps SDK for Qt. Happy coding! 

About the Author
I have been with Esri for over 21 years and I had the privilege of working on/with various software products including: ArcGIS Maps SDK for Qt, ArcGIS Maps SDK for .NET, ArcGIS Desktop, Arc Pro, ArcGIS Server, Map Objects, ArcView GIS, and even PC Arc/Info. Currently, I author content on the https://developers.arcgis.com web site helping developers be successful with Esri software.