![]()
Updates to Qt Quick for Android in Qt 6.9 and 6.10
Introduction to Qt Quick Enhancements for Android Development
As we prepare for the imminent release of Qt 6.11, it is essential to reflect on the significant advancements introduced in Qt 6.9 and Qt 6.10. These two releases have fundamentally reshaped the landscape of Android application development using the Qt framework. Specifically, the Qt Quick module has received critical updates that bridge the gap between native Java/Kotlin development and the declarative QML world. We have observed that developers building high-performance mobile applications require more granular control over the user interface and deeper integration with the Android operating system.
Qt 6.9 and 6.10 delivered exactly that. The updates focus on four primary pillars: partial data changes for efficient list handling, multi-view embedding from Android Services, multi-argument signals for robust communication, and comprehensive public Java APIs. These features are not merely incremental; they represent a paradigm shift in how we architect cross-platform applications. By leveraging these tools, developers can create Android applications that feel indistinguishable from their native counterparts while retaining the productivity benefits of the Qt ecosystem.
We will explore these updates in depth, analyzing the practical application of each feature and demonstrating how they can be utilized to build sophisticated, responsive, and maintainable Android applications.
Partial Data Changes: Revolutionizing QML List Handling
The Challenge of List Synchronization
In previous versions of Qt, updating a ListView or Repeater model often required a brute-force approach. If a single item in a data set changed, the standard QAbstractItemModel signals like dataChanged were typically emitted for a specific index. However, when dealing with complex QML views, this often triggered a re-evaluation of the entire delegate or, in worst-case scenarios, a reset of the view model. For large datasets, such as social media feeds or complex dashboards, this inefficiency resulted in noticeable UI jank and excessive CPU usage.
Implementing Partial Data Changes in Qt 6.9
With Qt 6.9, we introduced refined mechanisms for handling partial data changes. This update allows the underlying C++ model to communicate granular updates to the QML layer more effectively. We can now signal that only specific roles of specific items have changed, without disturbing the rest of the model.
In practice, this means using the dataChanged signal with a precise range of roles. When a QML view receives this signal, it limits its update scope strictly to the changed roles within the specified index range.
Optimizing Performance with Granular Updates
Consider a scenario where we have a list of users displaying an avatar, username, and online status. Previously, if a user’s online status changed, the entire delegate might have been re-bound. Now, we can emit a dataChanged signal specifying only the StatusRole.
// C++ Model implementation
QModelIndex topLeft = index(row, 0);
QModelIndex bottomRight = index(row, 0);
QVector<int> roles;
roles << StatusRole;
emit dataChanged(topLeft, bottomRight, roles);
In QML, the binding for the status indicator will trigger, while the bindings for the username and avatar remain untouched. This selective binding drastically reduces the JavaScript execution time on the UI thread, leading to buttery-smooth scrolling even on lower-end Android devices.
Practical Applications for Android
We have utilized partial data changes to build highly efficient data visualization tools. For instance, in a stock market ticker application, prices update every second. Without this feature, updating the price of a single stock would require refreshing the entire row. Now, we can update the price text and color indicator (red/green) independently. This efficiency extends battery life and reduces thermal throttling on mobile devices, which is a critical factor for user retention.
Embedding Multiple QML Views from Android Services
Bridging the Gap between Qt and Android Services
One of the most powerful updates in Qt 6.10 is the ability to embed multiple QML views originating from Android Services. Historically, Qt Quick was primarily designed to run within a single QGuiApplication context. While QAndroidBinder allowed for communication with background services, rendering QML content within those service contexts or managing multiple concurrent view hierarchies was cumbersome and unsupported.
Qt 6.10 introduces a robust mechanism to bind Qt Quick renderers to Android service components. This allows for true separation of concerns: the UI logic can reside in a foreground service, while the business logic operates in a background process, or multiple isolated UI fragments can be managed simultaneously.
Architectural Implementation
To utilize this, we leverage the QAndroidNativeInterface and the updated QtQuick backend. The key is initializing the QML engine within the context of an Android Service.
When an Android Service starts, it can now initialize a QGuiApplication (or a lightweight variant) and load a QML file. This QML file is rendered into a SurfaceTexture provided by the Android service.
Managing Multiple Views
The true power lies in the embedding multiple QML views capability. We can now run distinct QML engines—or multiple roots within a single engine—attached to different Android Activities or Windows.
- Background Processing UI: A service handling file uploads can display a progress bar and “cancel” button directly in the notification shade or a floating window.
- Split-Screen Logic: A navigation app can have the map view in one Activity (Service-backed) and the turn-by-turn instructions in another, running on different threads but sharing the same C++ backend.
Code Structure for Service Embedding
To implement this, we modify the standard Service class in Java/Kotlin to initialize the Qt environment.
// Java Implementation snippet
public class QtBackgroundService extends QtService {
@Override
public void onCreate() {
super.onCreate();
// Initialize Qt environment for this service context
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Load the QML specific to this service view
loadQml("qrc:/BackgroundUI.qml");
return START_STICKY;
}
}
This approach ensures that the UI remains responsive even if the main application thread is blocked. It is particularly useful for creating persistent dashboard widgets or floating chat heads on Android.
Multi-Argument Signals: Streamlining QML to C++ Communication
Evolution of Signal Handling
Signals are the backbone of Qt’s meta-object system. In earlier versions, connecting a QML signal to a C++ slot often required packaging multiple values into a QVariantList or creating wrapper structs. This added overhead and complexity, especially when dealing with complex data types.
Qt 6.10 introduces native support for multi-argument signals in QML. This allows developers to define signals in QML with multiple distinct parameters and connect them directly to C++ slots accepting those specific types.
Implementation in QML and C++
In QML, we can now define a signal with a clear signature:
// QML Definition
Item {
signal dataReceived(string id, int statusCode, real progress)
// Emitting the signal
function fetchData() {
dataReceived("req_123", 200, 0.5)
}
}
On the C++ side, the slot signature becomes strictly typed, improving type safety and runtime performance.
// C++ Slot
class DataHandler : public QObject {
Q_OBJECT
public slots:
void onDataReceived(const QString &id, int statusCode, double progress) {
// Process arguments directly without unpacking
qDebug() << "ID:" << id << "Status:" << statusCode;
}
};
Benefits for Type Safety and Refactoring
By utilizing multi-argument signals, we enforce stricter contracts between the UI and the business logic. The Qt Meta-Object Compiler (moc) validates these connections at compile time (in C++), significantly reducing runtime errors. This is a massive improvement for large-scale projects where maintaining signal-slot consistency across QML and C++ is a major source of bugs.
Furthermore, this feature simplifies the integration of asynchronous operations. A network request in C++ can emit a single signal containing the request ID, HTTP status, and downloaded data payload, which the QML layer receives and acts upon immediately.
Public Java APIs and Javadoc: Enhancing Native Integration
The Need for Exposed APIs
Qt for Android has always supported Java integration via JNI, but the APIs exposed to the Java side were often limited or undocumented. Developers frequently resorted to reflection or hacky workarounds to access Qt internals from Java or Kotlin.
Qt 6.9 and 6.10 marked a strategic shift toward public Java APIs. We have opened up significant portions of the Qt Android bindings, accompanied by comprehensive Javadoc documentation. This makes Qt a first-class citizen in the Android Studio ecosystem.
Key Classes and Utilities
With the new public APIs, we can interact with Qt components more naturally from Java.
- QtActivity/QtService Extensions: We can now subclass
QtActivityorQtServicewith greater confidence, as the lifecycle methods and helper functions are clearly documented. - Native Communication: The
QtNativeclass provides static methods to access the Qt application context, run runnables on the Qt thread, and access theQAndroidJniEnvironment.
Javadoc Integration
The inclusion of Javadoc means that Android Studio provides autocomplete and inline documentation for Qt classes. When we write a custom Java class that interacts with Qt, we can easily reference the official documentation.
For example, accessing the QAndroidJniObject from Java is now cleaner:
import org.qtproject.qt.android.bindings.QtActivity;
public class MyActivity extends QtActivity {
// We can now easily override methods with documented signatures
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Access public Qt utilities
}
}
This formalization of the Java layer bridges the cultural gap between Qt developers and Android developers. It allows mixed teams to collaborate more effectively, as the Android specialists can work within their familiar Java environment while leveraging Qt’s UI capabilities.
Practical Use Cases and Application Scenarios
High-Frequency Trading Dashboard
Combining partial data changes and multi-argument signals, we built a prototype trading dashboard. The C++ backend ingests WebSocket data and emits signals with multi-arguments (price, volume, timestamp). The QML frontend listens to these signals and updates specific list items using partial update roles. The result is a UI that handles thousands of updates per second without dropping frames, running on Android tablets.
IoT Control Center with Floating Controls
Using embedding multiple QML views, we created an IoT home automation app. The main view displays room layouts. A background service manages the WebSocket connection to IoT devices. This service spawns a floating QML view (using TYPE_APPLICATION_OVERLAY) that acts as a quick-access control panel. The floating view and the main view share the same C++ backend via the public Java APIs, ensuring state synchronization.
Media Player with Notification Controls
By leveraging public Java APIs, we integrated a Qt Quick media player with the Android MediaSession framework. The Java layer (extending QtService) handles the hardware media buttons and notification creation. It communicates with the QML layer using the newly supported multi-argument signals to update the play/pause state and track metadata displayed in the Android notification shade.
Migration Guide: Upgrading Existing Qt Android Projects
Updating CMakeLists.txt
When migrating to Qt 6.9 or 6.10, the first step is ensuring the CMakeLists.txt correctly links against the updated Android libraries. We recommend adding the following to ensure the new Java bindings are included:
find_package(Qt6 REQUIRED COMPONENTS Quick)
qt_standard_project_setup()
# Ensure Android extras are included
if(ANDROID)
qt_add_executable(AppName android SOURCES main.cpp)
else()
qt_add_executable(AppName main.cpp)
endif()
Refactoring Models for Partial Updates
Existing QAbstractItemModel implementations must be audited. If you are emitting dataChanged with an empty role list, the default behavior might trigger a full re-evaluation. We suggest updating all dataChanged emissions to include specific roles.
Before:
emit dataChanged(index(i), index(i));
After:
QVector<int> roles = {NameRole, StatusRole};
emit dataChanged(index(i), index(i), roles);
Java Activity Refactoring
Projects relying on custom Java code should review the deprecation warnings. Qt 6.9/6.10 deprecates some legacy helper methods in favor of the new public API. We advise migrating to the documented QtActivity lifecycle hooks.
Conclusion: The Future of Qt on Android
The updates in Qt 6.9 and 6.10 represent a massive leap forward for Qt Quick on Android. By focusing on partial data changes, we have unlocked new levels of UI performance. Embedding multiple QML views from services allows for complex, multi-process architectures that were previously impossible. Multi-argument signals have cleaned up our codebases, and public Java APIs have made Qt more accessible to the broader Android community.
As we look toward Qt 6.11, we are confident that these foundational updates will enable even more innovative applications. The barrier between native Android development and Qt development is lower than ever. Whether you are building a complex dashboard, a real-time communication tool, or a utility app, the tools provided in these releases empower you to deliver a superior user experience on the Android platform.
We encourage all developers to upgrade to Qt 6.10 immediately to take advantage of these features. The performance gains and development ergonomics are well worth the migration effort. For more technical details and code samples, refer to the official Qt documentation and our recent blog posts.