Android: Add documentation for QtAbstractItemModel Java API example

Adds documentation for Android Studio example application project.
Explains how to handle complex data types between Java and QML.
Explains the project structure and thread contextes with code examples.

Example opens up the usage of two Java API classes
QtAbstractItemModel (QtAIM) and QtModelIndex (QtMI).

Task-number: QTBUG-126457
Pick-to: 6.8
Change-Id: Ibbd10b74aa415985f3edd0affa8d0301d71aa8c0
Reviewed-by: Nicholas Bennett <nicholas.bennett@qt.io>
Reviewed-by: Rami Potinkara <rami.potinkara@qt.io>
This commit is contained in:
Konsta Alajärvi 2024-06-28 12:54:34 +03:00 committed by Rami Potinkara
parent 036beb9962
commit ce65c63748
5 changed files with 218 additions and 22 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,138 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\page qtabstractitemmodel-in-android-studio-projects-example.html
\title QtAbstractItemModel Example in Android Studio Projects
\brief Shows how to handle complex data types and apply conditional
formatting to a table view format using QtAbstractItemModel class and
QtModelIndex class.
\ingroup qtquickexamples
\section1 Overview
\image qtabstractitemmodel_portrait.png
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 \c QtAbstractItemModel and \c 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 \l {Qt Qml}.
\section1 Running the example
To run this example, you need Android Studio and
\l {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 \l {Qt Tools for Android Studio} to import the \c qtabstractitemmodel.
\section1 QML project
On the QML project side, the example uses a \l {Rectangle QML Type} as the root
object. The \c dataModel property variable holds the data model created and
delivered from the Java side.
\snippet android/models/qtabstractitemmodel/Main.qml 0
\l {TableView QML Type} displays our data model. On the \c delegate property, the example
defines each cell item of the model with \l {Text QML Type}.
\snippet android/models/qtabstractitemmodel/Main.qml 1
In the delegate, the example sets the \c row and \c column properties through the
\l {QAbstractItemModel Class}{QHash<int, QByteArray> QAbstractItemModel::roleNames() const}
and \l {QAbstractItemModel Class}{ QtModelIndex index(int row, int column, QtModelIndex parent) }
and \l {QAbstractItemModel Class}{Object data(QtModelIndex qtModelIndex, int role) }
methods of the model.
Calling these methods from QML means the execution takes place in the Qt qtMainLoopThread
thread context.
See \l {QAbstractItemModel Class}{QAbstractItemModel} for a detailed description.
\snippet android/models/qtabstractitemmodel/Main.qml 1
\section1 Android Studio project
The Android Studio project (qtabstractitemmodel_java) contains
one Activity class \c MainActivity and \c MyDataModel class.
\section2 Data Model
The data model, c\ MyDataModel, extends \c QtAbstractItemModel class.
The \c QtAbstractItemModel is a wrapper for \l {QAbstractItemModel Class}.
As the methods of \c 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 \c 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.
\snippet android/models/qtabstractitemmodel_java/app/src/main/java/com/example/qtabstractitemmodel_java/MyDataModel.java 1
The example overrides the \c 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.
\snippet android/models/qtabstractitemmodel_java/app/src/main/java/com/example/qtabstractitemmodel_java/MyDataModel.java 2
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.
\snippet android/models/qtabstractitemmodel_java/app/src/main/java/com/example/qtabstractitemmodel_java/MyDataModel.java 3
The example implements methods on the model side for \c 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 \c 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.
\snippet android/models/qtabstractitemmodel_java/app/src/main/java/com/example/qtabstractitemmodel_java/MyDataModel.java 4
The example implements methods on the model side for \c 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.
\snippet android/models/qtabstractitemmodel_java/app/src/main/java/com/example/qtabstractitemmodel_java/MyDataModel.java 5
\section2 Main Activity
\c MainActivity implements the \c 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
\l {Qt Quick View Android Class}{QtQuickView}
\snippet android/models/qtabstractitemmodel_java/app/src/main/java/com/example/qtabstractitemmodel_java/MainActivity.java 1
The example sets the UI button and its listeners to allow the users to interact with the
model via the UI.
\snippet android/models/qtabstractitemmodel_java/app/src/main/java/com/example/qtabstractitemmodel_java/MyDataModel.java 2
The example starts loading the QML content. Loading happens in the background until the
\c ready status is updated.
\snippet android/models/qtabstractitemmodel_java/app/src/main/java/com/example/qtabstractitemmodel_java/MyDataModel.java 3
The example sets the data model when the QML content is loaded, and the status is ready.
\snippet android/models/qtabstractitemmodel_java/app/src/main/java/com/example/qtabstractitemmodel_java/MyDataModel.java 4
*/

View File

@ -3,15 +3,17 @@
import QtQuick
import QtQuick.Controls
//! [0]
Rectangle {
id: mainRectangle
property AbstractItemModel dataModel
//! [0]
color: "#00414A"
border.color: "#00414A"
border.width: 2
//! [1]
TableView {
id: tableView
@ -29,7 +31,9 @@ Rectangle {
ScrollBar.horizontal: ScrollBar{
policy: ScrollBar.AsNeeded
}
//! [1]
//! [2]
delegate: Rectangle {
width: tableView.width
color: "#2CDE85"
@ -37,11 +41,12 @@ Rectangle {
border.width: 2
Text {
// Calls MyDataModel::data to get data based on the roles.
// Calls in a context of a QtRenderingThread TODO check and synch
// Called in Qt qtMainLoopThread thread context.
text: model.row + model.column
font.pixelSize: 26
font.bold: true
}
}
//! [2]
}
}

View File

@ -17,8 +17,9 @@ import org.qtproject.example.qtabstractitemmodel.QmlModule.Main;
public class MainActivity extends AppCompatActivity implements QtQmlStatusChangeListener {
private static final String TAG = "QtAIM MainActivity";
private Main m_mainQmlComponent;
//! [1]
private final MyDataModel m_model = new MyDataModel();
//! [1]
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -33,11 +34,12 @@ public class MainActivity extends AppCompatActivity implements QtQmlStatusChange
FrameLayout qmlFrameLayout = findViewById(R.id.qmlFrame);
qmlFrameLayout.addView(qtQuickView, params);
//! [2]
Button addRowAtEndButton = findViewById(R.id.addRowAtEndButton);
Button removeRowFromEndButton = findViewById(R.id.removeRowFromEndButton);
Button addColumnButton = findViewById(R.id.addColumnButton);
Button removeLastColumnButton = findViewById(R.id.removeLastColumnButton);
// Calls in a context of a Android thread.
// Calls in a context of a Android main thread.
addRowAtEndButton.setOnClickListener(view -> {
m_model.addRow();
});
@ -50,17 +52,22 @@ public class MainActivity extends AppCompatActivity implements QtQmlStatusChange
removeLastColumnButton.setOnClickListener(view -> {
m_model.removeColumn();
});
//! [2]
//! [3]
qtQuickView.loadComponent(m_mainQmlComponent);
//! [3]
}
//! [4]
@Override
public void onStatusChanged(QtQmlStatus qtQmlStatus) {
Log.i(TAG, "Status of QtQuickView: " + qtQmlStatus);
if (qtQmlStatus == QtQmlStatus.READY)
// Calls in a context of a Android thread.
// Calls in a context of a Android main thread.
m_mainQmlComponent.setDataModel(m_model);
}
//! [4]
}

View File

@ -28,22 +28,25 @@ public class MyDataModel extends QtAbstractItemModel {
private static final int ROLE_COLUMN = 1;
private static final int MAX_ROWS_AND_COLUMNS = 26;
private int m_columns = 4;
/* Two dimensional array of Cell objects to represent a sheet.
/*
* Two dimensional array of Cell objects to represent a sheet.
* First dimension are rows. Second dimension are columns.
* TODO QTBUG-127467
*/
private final ArrayList<ArrayList<Cell>> m_dataList = new ArrayList<>();
private final char m_firstLatinLetter = 'A';
// Called in Android 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
*/
//! [1]
/*
* 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() {
//! [1]
final int initializingRowAndColumnCount = m_columns;
for (int rows = 0 ; rows < initializingRowAndColumnCount; rows++) {
ArrayList<Cell> newRow = new ArrayList<>();
@ -56,18 +59,33 @@ public class MyDataModel extends QtAbstractItemModel {
}
}
//! [2]
/*
* Returns the count of columns.
* Threading: called in Android main thread context.
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
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
public int rowCount(QtModelIndex qtModelIndex) {
return m_dataList.size();
}
//! [2]
// Returns the data to QML based on the roleNames
// Called in QML Rendering Thread context
//! [3]
/*
* Returns the data to QML based on the roleNames
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
public Object data(QtModelIndex qtModelIndex, int role) {
switch (role) {
@ -84,7 +102,11 @@ public class MyDataModel extends QtAbstractItemModel {
return null;
}
}
// Function which defines what string in QML side can be used to get the data from Java side
/*
* Defines what string i.e. role in QML side gets the data from Java side.
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
public HashMap<Integer, String> roleNames() {
HashMap<Integer, String> roles = new HashMap<>();
@ -93,18 +115,30 @@ public class MyDataModel extends QtAbstractItemModel {
return roles;
}
/*
* Returns a new index model.
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
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
public QtModelIndex parent(QtModelIndex qtModelIndex) {
return new QtModelIndex();
}
//! [3]
// Four model side calls for MainActivity
// Called in Android Thread context
//! [4]
/*
* Adds a row.
* Threading: called in Android main thread context.
*/
public void addRow() {
if (m_columns > 0 && m_dataList.size() < MAX_ROWS_AND_COLUMNS) {
beginInsertRows(new QtModelIndex(), m_dataList.size(), m_dataList.size());
@ -113,7 +147,10 @@ public class MyDataModel extends QtAbstractItemModel {
}
}
// Called in Android Thread context
/*
* Removes a row.
* Threading: called in Android main thread context.
*/
public void removeRow() {
if (m_dataList.size() > 1) {
beginRemoveRows(new QtModelIndex(), m_dataList.size() - 1, m_dataList.size() - 1);
@ -121,8 +158,13 @@ public class MyDataModel extends QtAbstractItemModel {
endRemoveRows();
}
}
//! [4]
// Called in Android Thread context
//! [5]
/*
* Adds a column.
* Threading: called in Android main thread context.
*/
public void addColumn() {
if (!m_dataList.isEmpty() && m_columns < MAX_ROWS_AND_COLUMNS) {
beginInsertColumns(new QtModelIndex(), m_columns, m_columns);
@ -132,7 +174,10 @@ public class MyDataModel extends QtAbstractItemModel {
}
}
// Called in Android Thread context
/*
* Removes a column.
* Threading: called in Android main thread context.
*/
public void removeColumn() {
if (m_columns > 1) {
int columnToRemove = m_columns - 1;
@ -141,6 +186,7 @@ public class MyDataModel extends QtAbstractItemModel {
endRemoveColumns();
}
}
//! [5]
private void generateColumn() {
int amountOfRows = m_dataList.size();