- Qt 6.9
- Qt Quick
- Qt Quick for Android
- Using QtAbstractItemModel in Android Studio Projects
- Qt 6.9.3 Reference Documentation

Qt Quick for Android Studio Projects ◦ Exposing QtAbstractListModel to QML

# Using QtAbstractItemModel in Android Studio Projects

The Qt Quick for Android API examples are provided as Android Studio projects. The project folders are found in your Qt install location.

For example, under the default Windows install path, they are found here:

     C:\Qt\Examples\Qt-/1\platforms\android

## Overview

This example consists of two projects: an Android Studio project (qtabstractitemmodel_java) and a QML project (qtabstractitemmodel). You can import the QML project into an Android project.

The example shows how to handle complex data types between Java and QML. It demonstrates how to use the `QtAbstractItemModel` and `QtModelIndex` Java API classes. In QML, the data usage is demonstrated with the TableView item. In Java, data usage is demonstrated with a model of nested ArrayList items for rows and columns. For more information on how QML works, see Qt Qml.

## Running the example

To run this example, you need Android Studio and Qt Tools for Android Studio on top of a standard Qt for Android installation. Open qtabstractitemmodel_java in Android Studio and follow the instructions in Qt Tools for Android Studio to import the `qtabstractitemmodel`.

## QML project

On the QML project side, the example uses a Rectangle as the root object. The `dataModel` property variable holds the data model created and delivered from the Java side.

     Rectangle {
         id: mainRectangle

         property AbstractItemModel dataModel

TableView displays our data model.

         TableView {
             id: tableView

             model: mainRectangle.dataModel

             anchors {fill: parent; margins: 20}
             columnSpacing: 4
             rowSpacing: 6
             boundsBehavior: TableView.OvershootBounds
             clip: true

             ScrollBar.vertical: ScrollBar {
                policy: ScrollBar.AsNeeded
             }
             ScrollBar.horizontal: ScrollBar{
                policy: ScrollBar.AsNeeded
             }

On the `delegate` property, each cell item of the model is defined with a Rectangle containing a TextEdit. The text property of the `TextEdit` is set using the QAbstractItemModel::data() which returns a value based on the given role and index.

Calling these methods from QML means the execution takes place in the Qt qtMainLoopThread thread context.

             delegate: Rectangle {
                 implicitWidth: (tableView.height > tableView.width) ? tableView.width / 10 : tableView.height / 5
                 implicitHeight: implicitWidth

                 required property var model

                 color: "#2CDE85"
                 border {color: "#00414A"; width: 2}

                 TextEdit {
                     // Calls MyDataModel::data to get data based on the roles.
                     // Called in Qt qtMainLoopThread thread context.
                     //
                     // After editing is finished, call MyDataModel::setData()
                     // to update the value of selected cell.
                     onEditingFinished: parent.model.edit = text

                     text: parent.model.display
                     font {pixelSize: 26; bold: true}
                     padding: 5
                     anchors.fill: parent
                     wrapMode: TextEdit.Wrap
                     horizontalAlignment: TextEdit.AlignHCenter
                     verticalAlignment: TextEdit.AlignVCenter
                 }
             }

In the case of editing the TextEdit field, `onEditingFinished()` handler sets the model's `edit` role value to the edited text. This calls the QAbstractItemModel::setData() method, where the edited text of the cell is updated to the corresponding index of the model.

For more information see QAbstractItemModel.

## Android Studio project

The Android Studio project (qtabstractitemmodel_java) contains one Activity class `MainActivity` and `MyDataModel` class.

### Data Model

The data model, `MyDataModel`, extends `QtAbstractItemModel` class. The `QtAbstractItemModel` is a wrapper for QAbstractItemModel.

As the methods of `MyDataModel` class are called from both, QML and Android sides, the execution occurs in both thread contexts, Qt qtMainLoopThread, and Android main thread contexts. You must ensure synchronization when accessing member variables in methods of the `MyDataModel` class.

First, the example initializes the model with a simple row and column mock data set. Note that this constructor method is called in the Android main thread context.

     /*
     * Initializes the two-dimensional array list with following content:
     * [] [] [] [] 1A 1B 1C 1D
     * [] [] [] [] 2A 2B 2C 2D
     * [] [] [] [] 3A 3B 3C 3D
     * [] [] [] [] 4A 4B 4C 4D
     * Threading: called in Android main thread context.
     */
     public MyDataModel() {

The example overrides the `QtAbstractItemModel` methods for different purposes. The columnCount() and rowCount() methods return the count of each in a model. The execution of each rowCount() occurs in both thread contexts, Qt qtMainLoopThread and Android main thread contexts.

     /*
     * Returns the count of columns.
     * Threading: called in Android main thread context.
     * Threading: called in Qt qtMainLoopThread thread context.
     */
     @Override
     synchronized public int columnCount(QtModelIndex qtModelIndex) {
         return m_columns;
     }

     /*
     * Returns the count of rows.
     * Threading: called in Android main thread context.
     * Threading: called in Qt qtMainLoopThread thread context.
     */
     @Override
     synchronized public int rowCount(QtModelIndex qtModelIndex) {
         return m_dataList.size();
     }

Method data() provides model data based on the role and index from Java to QML. The roleNames() method returns a hash matching numerical role values to their names as strings; in QML, we use these role names to fetch corresponding data from the model. The index() method returns the new model index. Method parent() should return a parent of the index. Still, as this example focuses on data without parent indices, we override the method and return an empty QtModelIndex(). As the methods are called from QML, the execution occurs in the Qt qtMainLoopThread thread context.

     /*
     * Returns the data to QML based on the roleNames
     * Threading: called in Qt qtMainLoopThread thread context.
     */
     @Override
     synchronized public Object data(QtModelIndex qtModelIndex, int role) {
         if (role == ROLE_DISPLAY) {
             Cell elementForEdit = m_dataList.get(qtModelIndex.row()).get(qtModelIndex.column());
             return elementForEdit.getValue();
         }
         Log.w(TAG, "data(): unrecognized role: " + role);
         return null;
     }

     /*
     * Defines what string i.e. role in QML side gets the data from Java side.
     * Threading: called in Qt qtMainLoopThread thread context.
     */
     @Override
     synchronized public HashMap<Integer, String> roleNames() {
         HashMap<Integer, String> roles = new HashMap<>();
         roles.put(ROLE_DISPLAY, "display");
         roles.put(ROLE_EDIT, "edit");
         return roles;
     }

     /*
     * Returns a new index model.
     * Threading: called in Qt qtMainLoopThread thread context.
     */
     @Override
     synchronized public QtModelIndex index(int row, int column, QtModelIndex parent) {
         return createIndex(row, column, 0);
     }

     /*
     * Returns a parent model.
     * Threading: not used called in this example.
     */
     @Override
     synchronized public QtModelIndex parent(QtModelIndex qtModelIndex) {
         return new QtModelIndex();
     }

The example overrides the QAbstractItemModel::setData() method, which is called when model's data at `index` is set from the QML side of the application.

     /*
     * Gets called when model data is edited from QML side.
     * Sets the role data for the item at index to value,
     * if given index is valid and if data in given index truly changed.
     */
     @Override
     synchronized public boolean setData(QtModelIndex index, Object value, int role) {
         Cell cellAtIndex = m_dataList.get(index.row()).get(index.column());
         String cellValueAtIndex = cellAtIndex.getValue();
         if (!index.isValid() || role != ROLE_EDIT
                 || Objects.equals(cellValueAtIndex, value.toString())) {
             return false;
         }
         cellAtIndex.setValue(value.toString());
         // Send dataChanged() when data was successfully set.
         dataChanged(index, index, new int[]{role});
         return true;
     }

The example implements methods on the model side for `MainActivity` UI interaction to add and remove rows and columns. Calls begin, end, insert, and remove rows to update model indexes, like beginInsertRow(). Because the example uses the `QtAbstractItemModel`, it must call beginInsertRows() and endInsertRows() every time it inserts new rows into the model. The same applies to removal. As the methods are called from the Android side, the execution takes place in the Android main thread context.

     /*
     * Adds a row.
     * Threading: called in Android main thread context.
     */
     synchronized public void addRow() {
         if (m_columns > 0 && m_dataList.size() < MAX_ROWS_AND_COLUMNS) {
             beginInsertRows(new QtModelIndex(), m_dataList.size(), m_dataList.size());
             m_dataList.add(generateNewRow());
             endInsertRows();
         }
     }

     /*
     * Removes a row.
     * Threading: called in Android main thread context.
     */
     synchronized public void removeRow() {
         if (m_dataList.size() > 1) {
             beginRemoveRows(new QtModelIndex(), m_dataList.size() - 1, m_dataList.size() - 1);
             m_dataList.remove(m_dataList.size() - 1);
             endRemoveRows();
         }
     }

The example implements methods on the model side for `MainActivity` UI interaction to add and remove columns. Calls begin, end, insert, and remove columns to update model indexes, like beginRemoveColumn(). The same context awareness applies as with the add and remove row methods.

     /*
     * Adds a column.
     * Threading: called in Android main thread context.
     */
     synchronized public void addColumn() {
         if (!m_dataList.isEmpty() && m_columns < MAX_ROWS_AND_COLUMNS) {
             beginInsertColumns(new QtModelIndex(), m_columns, m_columns);
             generateNewColumn();
             m_columns += 1;
             endInsertColumns();
         }
     }

     /*
     * Removes a column.
     * Threading: called in Android main thread context.
     */
     synchronized public void removeColumn() {
         if (m_columns > 1) {
             int columnToRemove = m_columns - 1;
             beginRemoveColumns(new QtModelIndex(), columnToRemove, columnToRemove);
             for (int row = 0; row < m_dataList.size(); row++)
                 m_dataList.get(row).remove(columnToRemove);
             m_columns -= 1;
             endRemoveColumns();
         }
     }

### Main Activity

`MainActivity` implements the `QtQmlStatusChangeListener` interface to get status updates when the QML is loaded. It is also the main Android activity.

The example creates and initializes the data model. See also QtQuickView

     private final MyDataModel m_model = new MyDataModel();

The example sets the UI button and its listeners to allow the users to interact with the model via the UI.

     /*
     * Returns the count of columns.
     * Threading: called in Android main thread context.
     * Threading: called in Qt qtMainLoopThread thread context.
     */
     @Override
     synchronized public int columnCount(QtModelIndex qtModelIndex) {
         return m_columns;
     }

     /*
     * Returns the count of rows.
     * Threading: called in Android main thread context.
     * Threading: called in Qt qtMainLoopThread thread context.
     */
     @Override
     synchronized public int rowCount(QtModelIndex qtModelIndex) {
         return m_dataList.size();
     }

The example starts loading the QML content. Loading happens in the background until the `ready` status is updated.

     /*
     * Returns the data to QML based on the roleNames
     * Threading: called in Qt qtMainLoopThread thread context.
     */
     @Override
     synchronized public Object data(QtModelIndex qtModelIndex, int role) {
         if (role == ROLE_DISPLAY) {
             Cell elementForEdit = m_dataList.get(qtModelIndex.row()).get(qtModelIndex.column());
             return elementForEdit.getValue();
         }
         Log.w(TAG, "data(): unrecognized role: " + role);
         return null;
     }

     /*
     * Defines what string i.e. role in QML side gets the data from Java side.
     * Threading: called in Qt qtMainLoopThread thread context.
     */
     @Override
     synchronized public HashMap<Integer, String> roleNames() {
         HashMap<Integer, String> roles = new HashMap<>();
         roles.put(ROLE_DISPLAY, "display");
         roles.put(ROLE_EDIT, "edit");
         return roles;
     }

     /*
     * Returns a new index model.
     * Threading: called in Qt qtMainLoopThread thread context.
     */
     @Override
     synchronized public QtModelIndex index(int row, int column, QtModelIndex parent) {
         return createIndex(row, column, 0);
     }

     /*
     * Returns a parent model.
     * Threading: not used called in this example.
     */
     @Override
     synchronized public QtModelIndex parent(QtModelIndex qtModelIndex) {
         return new QtModelIndex();
     }

The example sets the data model when the QML content is loaded, and the status is ready.

     /*
     * Gets called when model data is edited from QML side.
     * Sets the role data for the item at index to value,
     * if given index is valid and if data in given index truly changed.
     */
     @Override
     synchronized public boolean setData(QtModelIndex index, Object value, int role) {
         Cell cellAtIndex = m_dataList.get(index.row()).get(index.column());
         String cellValueAtIndex = cellAtIndex.getValue();
         if (!index.isValid() || role != ROLE_EDIT
                 || Objects.equals(cellValueAtIndex, value.toString())) {
             return false;
         }
         cellAtIndex.setValue(value.toString());
         // Send dataChanged() when data was successfully set.
         dataChanged(index, index, new int[]{role});
         return true;
     }