Qt QML best practices

As you continue developing apps, you should know a few details that can make your apps perform better and encounter fewer problems. This topic discusses important details that you should keep in mind.

Map view

The ArcGIS Runtime SDK for Qt offers three patterns for displaying a map in a map view. In AppStudio, you will write your app in QML, so you will use the MapView map type.

The QML API from The Qt Company allows you to write cross-platform apps with QML, a declarative, highly readable syntax for designing and building responsive and fluid user interfaces for native desktop and mobile applications. ArcGIS Runtime SDK for Qt extends QML with QML types that provide ArcGIS Runtime functionality. Objects are declared hierarchically and have bindable properties to provide automatically dynamic behavior. JavaScript functions are used to provide procedural code when needed. This is an important feature for developers who are already familiar with web development and want to develop native apps.

Declare a Rectangle containing a MapView. In the MapView, declare a Map to display and an initial Viewpoint.

Rectangle {
    width: 800
    height: 600
     property real scaleFactor: System.displayScaleFactor
     // Map view UI presentation at top
    MapView {
        id: mv
         anchors.fill: parent
        wrapAroundMode: Enums.WrapAroundModeDisabled
         Map {
            BasemapTopographic {}
            initialViewpoint: viewPoint
             FeatureLayer {
                id: featureLayer
                 ServiceFeatureTable {
                    id: featureTable
                    url: "http://sampleserver6.arcgisonline.com/arcgis/rest/services/SF311/FeatureServer/0"
                }
            }
        }
         ViewpointCenter {
            id: viewPoint
            center: Point {
                x: -13630484
                y: 4545415
                spatialReference: SpatialReference {
                    wkid: 102100
                }
            }
            targetScale: 300000
        }
    }

Strongly typed variables

When declaring properties in QML, it's easy and convenient to use the generic variable type var, however, it is always better to use strongly typed variables. A strongly typed variable explicitly states what type of data can be stored in the variable, for example, an integer, a string, or a decimal value. Strongly typed variables can prevent the assignment of wrong values, are easier to read, and easier to debug compared variables of type var. A variable of type var would not prevent a decimal value being assigned to it, even if the value was only supposed to hold integers.

In the following example, the integer variable intValue is initially assigned a value of 10. Later in the code, the same variable is updated with a string value.

property int intValue = 10
...
intValue = "A string value"

When this code is run, an error will display in the console that refers explicitly to the line number where the string value was set, and stating Error: Cannot assign QString to int, making the code easier to troubleshoot.

If a value with the wrong type is assigned to a variable of type var, an error would not be reported.

Scalable user interfaces

When designing the user interface of your app, it is important to consider the ever evolving range of screen sizes and resolutions that your app may be used on. This can be achieved by including icons, buttons, and backgrounds for each display resolution, or by using scalable vector graphics (SVG).

When including a set of images at different resolutions, choose a file naming convention that clearly differentiates the image sizes. For example: image-name1x.png, image-name1.5x.png, image-name2x.png represent images that are 1 times base DPI, 1.5 times base DPI, and 3 times base DPI.

SVG files are ideal for small images, typically icons. Large SVG files can be slow to render, so consider using images for backgrounds.

For more information, see Qt's Scalable User Interfaces.

Debugging your app

AppStudio is built on top of the Qt Framework, which means you have access to all of the same debugging utilities used with Qt. Below are some helpful tips and tricks for debugging your Qt apps.

Using a web proxy

When debugging AppStudio apps, a web debugging utility is often useful to capture and analyze the HTTP requests that are sent and responses that are returned. Fiddler and Charles are two examples of web debugging utilities used to perform this task. You can use these tools to debug an app by adding the following C++ code in your app: QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::HttpProxy, "127.0.0.1", 8888));. This code can be set at runtime and can be either in your main.cpp or in some other C++ class. Beyond using a proxy for debugging, QNetworkProxy::setApplicationProxy() can also be used to specify your organization's proxy server. To do this, specify the URL and port of your organization's proxy server. Further information can be found in the AppFramework NetworkProxy documentation.

Using JSON.stringify()

When using the JSON.stringify() method, avoid passing in the object reference itself. Use the json property of the object as the argument, for example: JSON.stringify(graphic.geometry.json).

QML API memory model

The memory model used for QML apps is based on garbage collection. Unreferenced variables will be cleaned up by the QML Engine garbage collector. Therefore, all objects must be explicitly referenced to avoid garbage collection when they are still in use.

Object creation using JavaScript

The ArccGISRuntimeEnvironment.createObject() method creates and returns new instances of an object through the QML engine. The lifetime of these instances is determined by the instance's parent object. When the parent has garbage collected, so will its child instances. There are various ways to persist an instance created with ArccGISRuntimeEnvironment.createObject(), including the following:

  • You can specify the parent object in the third, optional parameter of ArccGISRuntimeEnvironment.createObject().
  • In your QML declarative code, declare the object as a property of another object. Then in JavaScript code, instantiate the object with ArccGISRuntimeEnvironment.createObject(). The class containing the property becomes the parent of the new instance. Be sure to declare the property to be of the same ArcGIS Runtime QML type as you are instantiating.

If you do not specify a parent, JavaScript is set as the object instance's parent. QML tracks the instance and deletes it when there are no remaining JavaScript references to the instance (that is, when the instance goes out of scope.)

Some ArcGIS Runtime QML types include a clone() method used to create an object instance of that type. When you use clone() to create an instance, JavaScript is its parent. You can change the parent using the parent property.

Working with models

Models access object instances explicitly. When working with models, it is necessary to provide persisted data for the model to access. Placing newly created object instances directly into a QML ListModel is not sufficient to maintain the lifetime of those instances, because doing so does not reference count those instances. (See the previous section about object creation using JavaScript.) One way to avoid garbage collection is to create a property that is a list, and add the instances to the persisted list. The lifetime of the list property is that of its parent.

// In a QML declaration:
ListModel {
  id: myListModel
  property var geoms: []
}

//...
// In a JavaScript function:
var newPoint = point.clone();

// avoid the following, because newPoint is not reference counted 
// myListModel.append(
//    {"geometry", newPoint
//    });  

// do this instead to give newPoint the same lifetime as myListModel
myListModel.geoms.push(newPoint);

This concept also applies to list object instances based on QQmlListProperty. Although you can directly assign such a list as input for a model, you should avoid doing this. The model will create nonexplicit references to the list items, so the items will be garbage collected. To get the correct results, first assign all the list elements to a local list. These explicit references will persist and avoid early garbage collection. The following example builds a list component and inserts all the fields from the feature table. Then, it sets the list as the model of the ComboBox.

GeodatabaseFeatureTable {
    id: featureTable
}

ComboBox {
    id: comboBox
//    model: featureTable.fields // avoid this, because the model will create non-explicit references
    textRole: "name"
}

property var fieldList: []

function initFieldListModel() {
    for (var i = 0; i < featureTable.fields.length; ++i) {
        fieldList.push(featureTable.fields[i]);
    }
    comboBox.model = fieldList;
}

Object uniqueness

There is no guarantee that you will get the same object back even for the same getter or function call. As such, direct object comparison should be avoided, for example:

Map { id: map }
...
var viewpoint = map.initialViewpoint;
// Avoid comparisons like the following code.
// Although the objects' contents are identical, 
// the objects may be different objects.
if (viewpoint === map.initialViewpoint)

Custom properties on QML types

A custom (user-defined) property is one that you add to an instance of a QML type when you declare it. The property is not part of the type but exists as a custom property on the specific instance. The QML language allows you to add custom properties to instances when you declare them. While you can add custom properties to ArcGIS Runtime QML type instances, that property may not be preserved in all cases due to the way instances are maintained in the API. Therefore, avoid using custom properties on instances of types that do not support them.

The following QML types, and types derived from these types, support custom properties. Other ArcGIS Runtime QML types do not.

  • ArcGISMapServiceInfo
  • Basemap
  • Credential
  • FeatureTable
  • Geodatabase
  • GraphicsOverlay
  • LayerContent
  • Map
  • Portal
  • PortalTask
  • VectorTileSourceInfo

Working with JSON

Many QML types inherit from JsonSerializable, allowing you to serialize the type, or populate its contents, in JSON. When creating an object from JSON, create an instance first, then pass that instance as input to the method.

This JavaScript code creates and populates a PictureMarkerSymbol from JSON and uses it in a new graphic.

Graphic {
  id: myGraphic


  property var pmsJson: {"type": "esriPMS", "url": "http://myurl.com/folder/my-icon.png","width": 60,"height": 60, "angle":20}


  Component.onCompleted: {
    var pmsFromJson = ArcGISRuntimeEnvironment.createObject("PictureMarkerSymbol", {json: pmsJson});
    pmsFromJson.errorChanged.connect(function(error){ verify(false, error.message + " (" + error.additionalMessage + ")"); });
    myGraphic.symbol = pmsFromJson;
  }
}

Working with translated strings

All strings that are displayed in the UI of an app should use qsTr() formatting to ensure that you can globalize your app.

It is also important to consider translated strings when you are defining your app logic. If a user will select an option based on strings in the UI, for example a selection from a drop-down menu, each case inside the switch statement must use the translated string.

In the following example, the about panel will be shown when the About menu item is selected, regardless of which language the app is displaying:

case qsTr("About"):
    pageView.hideSearchItem()
    panelDockItem.addDock("aboutPanel")
    break

OpenGL or ANGLE rendering on Windows

Qt has two different rendering engines for the Windows platform: OpenGL and DirectX through ANGLE. By default, Qt will try to render using OpenGL. If the proper drivers are not available, it will fall back to using ANGLE for rendering. Previous releases of ArcGIS Runtime only supported ANGLE. Starting with ArcGIS Runtime 100.3.0, your apps can fully support both OpenGL and ANGLE so that you can use either rendering engine and the Qt fallback mechanism when OpenGL cannot be used on a given system. For more information, see Qt's documentation.

To choose between OpenGL and ANGLE rendering as a default, use the Settings > Platforms > Windows > Graphics Rendering Engine options in AppStudio. No code changes are needed.