Android: Add documentation for QtAbstractListModel example

This change includes code snippet markers, some minimal code changes
(moving code around for snippet clarity), a screenshot of the example
and the main documentation page for the example.

Task-number: QTBUG-126841
Pick-to: 6.8
Change-Id: I2a10571f37a70a55b8411f1b7989b47810639386
Reviewed-by: Nicholas Bennett <nicholas.bennett@qt.io>
This commit is contained in:
Petri Virkkunen 2024-08-07 10:53:52 +03:00
parent 7afacee589
commit 50aea1f6a3
7 changed files with 222 additions and 53 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,131 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\page qtabstractlistmoder-kotlin-example.html
\title Exposing QtAbstractListModel to QML
\brief Uses a \l QtAbstractListModel to share data between Android code and
a QML view.
\ingroup qtquickexamples
\section1 Overview
\image qtabstractlistmodel_portrait.png
This example consists of two separate projects: A QML project and a Kotlin-based Android
project which will host and display the QML content. It shows how to use \l QtAbstractListModel
to share data from the Android side to the QML view which displays the data using a
\l ListView.
\section1 Running the example
To run this example, you need Android Studio and a \l {Qt for Android} installation.
The \l {Qt Gradle Plugin} will be used to build the QML project during the Android project
build process. For this, the example has some plugin configuration in the app-level
build.gradle file which may need to be modified if the plugin cannot, for example, find the Qt
kit directory.
\snippet android/models/qtabstractlistmodel_kotlin/app/build.gradle build.gradle QtBuild config
For further configuration of the plugin, please refer to the
\l [Qt Gradle Plugin]{Configure the plugin}{Qt Gradle Plugin documentation}.
\section1 QML project
The QML project is quite simple, it defines a data model as a property of the root object
and some UI elements to display the data from that model.
\snippet android/models/qtabstractlistmodel/Main.qml QML root item and dataModel definition
To display the data from the model, a ListView is created. The \c model property is then set
to the data model declared earlier.
\snippet android/models/qtabstractlistmodel/Main.qml ListView definition
In order to display the data model, the ListView needs a delegate which will be instantiated
for each item in the data model. In this case, the delegate will be a \l Rectangle that holds
two \l Text elements in a \l Column, displaying the data from each element in the data model.
\snippet android/models/qtabstractlistmodel/Main.qml ListView delegate definition
\section1 Kotlin project
The Android side consists of a single \l {Android: Activity} {Activity} and the definition for the data model used
earlier in the QML view.
\section2 Data model
The data model \c MyListModel is a child class of QtAbstractListModel, with
\c ArrayList<String> as the internal storage system for data. In the initializer block of
\c MyListModel, it generates some random data for the list.
\snippet android/models/qtabstractlistmodel_kotlin/app/src/main/java/com/example/qtlm_in_kotlin_based_android_project/MyListModel.kt MyListModel definition
Each item in the model has a set of data elements associated with it, each with its own role.
Custom implementations of QtAbstractItemModel must define a custom role for each data
element. Each role has an associated \l {Kotlin: Int} {Int} value, which is used when
retrieving the data, and a \l {Kotlin: String} {String} value, which specifies the name of the
data element when used from QML.
\snippet android/models/qtabstractlistmodel_kotlin/app/src/main/java/com/example/qtlm_in_kotlin_based_android_project/MyListModel.kt MyListModel::roleNames
While the \c Int values in the \c "roleNames()" method may be hard-coded, this example
specifies a custom enum class \c DataRole within \c MyListModel, which is used when referring
to these values. In this example, we define two roles: UUID and Row.
\snippet android/models/qtabstractlistmodel_kotlin/app/src/main/java/com/example/qtlm_in_kotlin_based_android_project/MyListModel.kt MyListModel::DataRole enum
When it comes to returning data from the data model, the class must override the
\c "QtAbstractListModel::data()" method. This method takes two parameters: \l QtModelIndex and
\c Int, which refer to the index and role of the data element, respectively.
In \c "MyDataModel::data()", the \c UUID role returns the data from the given index in the
internal data, while the \c Row role returns the row of the requested element.
\note This method, along with some others, is annotated with a
\l {Kotlin: Synchronized} {@Synchronized} tag. This is due to calls to these methods
originating from the Qt thread and accessing the underlying data possibly at the same time as
requests from the Android thread via the \c "addRow()" and \c "removeRow()" methods.
\snippet android/models/qtabstractlistmodel_kotlin/app/src/main/java/com/example/qtlm_in_kotlin_based_android_project/MyListModel.kt MyListModel::data
To allow outside actors to manipulate the QtAbstractItemModel, the example adds two additional
methods to \c MyDataModel. To add data to the row, it has the \c "addRow()" method; to remove
data, there is the \c "removeRow()" method. These are used from the main activity.
\snippet android/models/qtabstractlistmodel_kotlin/app/src/main/java/com/example/qtlm_in_kotlin_based_android_project/MyListModel.kt MyListModel::addRow
\snippet android/models/qtabstractlistmodel_kotlin/app/src/main/java/com/example/qtlm_in_kotlin_based_android_project/MyListModel.kt MyListModel::removeRow
\section2 Main activity
The \c MainActivity class is a simple Kotlin-based Activity but also implements the
interface \c QtQmlStatusChangeListener to listen to QML loading status events. It also
stores the \c QtQmlComponent object for the main view of the QML application and an instance
of the data model detailed above.
\snippet android/models/qtabstractlistmodel_kotlin/app/src/main/java/com/example/qtlm_in_kotlin_based_android_project/MainActivity.kt MainActivity definition
When creating the main Activity of the application, the example first creates a \l QtQuickView
and places it into the view hierarchy.
\snippet android/models/qtabstractlistmodel_kotlin/app/src/main/java/com/example/qtlm_in_kotlin_based_android_project/MainActivity.kt Adding QtQuickView
After adding the QtQuickView into the UI, the example finds the buttons that are used to
manipulate the data model and sets some click listeners to call \c addRow() and \c removeRow()
on the member data model.
\snippet android/models/qtabstractlistmodel_kotlin/app/src/main/java/com/example/qtlm_in_kotlin_based_android_project/MainActivity.kt Adding control buttons
Once the UI setup and listeners are done, the QML component can be prepared and loaded. The
example sets the \c MainActivity as a listener for the status change signal of the QML
component and tells \c QtQuickView to load the QML component.
\snippet android/models/qtabstractlistmodel_kotlin/app/src/main/java/com/example/qtlm_in_kotlin_based_android_project/MainActivity.kt Loading the QML component
Finally, once the QML component is successfully loaded, the example assigns the value of the
MyDataModel instance into the \c dataModel property in the QML component.
\snippet android/models/qtabstractlistmodel_kotlin/app/src/main/java/com/example/qtlm_in_kotlin_based_android_project/MainActivity.kt Linking the data model
*/

View File

@ -3,19 +3,23 @@
import QtQuick
import QtQuick.Controls
//! [QML root item and dataModel definition]
Rectangle {
id: mainRectangle
property AbstractItemModel dataModel
//! [QML root item and dataModel definition]
color: "#00414A"
border.width: 2
border.color: "black"
//! [ListView definition]
ListView {
id: listView
model: mainRectangle.dataModel
//! [ListView definition]
ScrollBar.vertical: ScrollBar {}
spacing: 10
@ -24,6 +28,7 @@ Rectangle {
margins: 20
}
//! [ListView delegate definition]
delegate: Rectangle {
required property var model
@ -65,5 +70,6 @@ Rectangle {
}
}
}
//! [ListView delegate definition]
}
}

View File

@ -4,11 +4,13 @@ plugins {
id 'org.qtproject.qt.gradleplugin' version '0.1-SNAPSHOT+'
}
//! [build.gradle QtBuild config]
QtBuild {
// Relative for pre-installed (Installer or MaintenanceTool) installations.
qtPath file('../../../../../../../6.8.0')
projectPath file('../../qtabstractlistmodel')
}
//! [build.gradle QtBuild config]
android {
namespace 'com.example.qtabstractlistmodel_kotlin'

View File

@ -14,23 +14,25 @@ import org.qtproject.qt.android.QtQmlStatus
import org.qtproject.qt.android.QtQmlStatusChangeListener
import org.qtproject.qt.android.QtQuickView
//! [MainActivity definition]
class MainActivity : AppCompatActivity(), QtQmlStatusChangeListener {
private val m_mainQmlComponent: Main = Main()
private val m_listModel = MyListModel()
//! [MainActivity definition]
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//! [Adding QtQuickView]
val qtQuickView: QtQuickView = QtQuickView(this)
m_mainQmlComponent.setStatusChangeListener(this)
val params: ViewGroup.LayoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
)
val qmlFrameLayout: FrameLayout = findViewById<FrameLayout>(R.id.qmlFrame)
qmlFrameLayout.addView(qtQuickView, params)
//! [Adding QtQuickView]
//! [Adding control buttons]
val addRowAtEndButton: Button = findViewById<Button>(R.id.addRow)
val removeRowFromEndButton: Button = findViewById<Button>(R.id.removeRow)
addRowAtEndButton.setOnClickListener { _: View? ->
@ -39,13 +41,18 @@ class MainActivity : AppCompatActivity(), QtQmlStatusChangeListener {
removeRowFromEndButton.setOnClickListener { _: View? ->
m_listModel.removeRow()
}
//! [Adding control buttons]
//! [Loading the QML component]
m_mainQmlComponent.setStatusChangeListener(this)
qtQuickView.loadComponent(m_mainQmlComponent)
//! [Loading the QML component]
}
//! [Linking the data model]
override fun onStatusChanged(qtQmlStatus: QtQmlStatus) {
if (qtQmlStatus === QtQmlStatus.READY) {
m_mainQmlComponent.setDataModel(m_listModel)
}
}
//! [Linking the data model]
}

View File

@ -7,7 +7,66 @@ import java.util.UUID
import org.qtproject.qt.android.QtAbstractListModel
import org.qtproject.qt.android.QtModelIndex
//! [MyListModel definition]
class MyListModel : QtAbstractListModel() {
private val m_dataList = ArrayList<String>()
init {
synchronized(this) {
for (row in 0..4) {
m_dataList.add(UUID.randomUUID().toString())
}
}
}
//! [MyListModel definition]
//! [MyListModel::data]
@Synchronized
override fun data(qtModelIndex: QtModelIndex, role: Int): Any {
return when (DataRole.valueOf(role)) {
DataRole.UUID -> "UUID: " + m_dataList[qtModelIndex.row()]
DataRole.Row -> "Row: " + qtModelIndex.row()
else -> ""
}
}
//! [MyListModel::data]
@Synchronized
override fun rowCount(qtModelIndex: QtModelIndex?): Int {
return m_dataList.size
}
//! [MyListModel::roleNames]
@Synchronized
override fun roleNames(): HashMap<Int, String> {
val m_roles = HashMap<Int, String>()
m_roles[DataRole.UUID.value()] = "id"
m_roles[DataRole.Row.value()] = "row"
return m_roles
}
//! [MyListModel::roleNames]
//! [MyListModel::addRow]
@Synchronized
fun addRow() {
beginInsertRows(QtModelIndex(), m_dataList.size, m_dataList.size)
m_dataList.add(UUID.randomUUID().toString())
endInsertRows()
}
//! [MyListModel::addRow]
//! [MyListModel::removeRow]
@Synchronized
fun removeRow() {
if (!m_dataList.isEmpty()) {
beginRemoveRows(QtModelIndex(), m_dataList.size - 1, m_dataList.size - 1)
m_dataList.removeAt(m_dataList.size - 1)
endRemoveRows()
}
}
//! [MyListModel::removeRow]
//! [MyListModel::DataRole enum]
private enum class DataRole(val m_value: Int) {
UUID(0),
Row(1);
@ -24,52 +83,5 @@ class MyListModel : QtAbstractListModel() {
}
}
}
private val m_dataList = ArrayList<String>()
init {
synchronized(this) {
for (row in 0..4) {
m_dataList.add(UUID.randomUUID().toString())
}
}
}
@Synchronized
override fun data(qtModelIndex: QtModelIndex, role: Int): Any {
return when (DataRole.valueOf(role)) {
DataRole.UUID -> "UUID: " + m_dataList[qtModelIndex.row()]
DataRole.Row -> "Row: " + qtModelIndex.row()
else -> ""
}
}
@Synchronized
override fun rowCount(qtModelIndex: QtModelIndex?): Int {
return m_dataList.size
}
@Synchronized
override fun roleNames(): HashMap<Int, String> {
val m_roles = HashMap<Int, String>()
m_roles[DataRole.UUID.value()] = "id"
m_roles[DataRole.Row.value()] = "row"
return m_roles
}
@Synchronized
fun addRow() {
beginInsertRows(QtModelIndex(), m_dataList.size, m_dataList.size)
m_dataList.add(UUID.randomUUID().toString())
endInsertRows()
}
@Synchronized
fun removeRow() {
if (!m_dataList.isEmpty()) {
beginRemoveRows(QtModelIndex(), m_dataList.size - 1, m_dataList.size - 1)
m_dataList.removeAt(m_dataList.size - 1)
endRemoveRows()
}
}
//! [MyListModel::DataRole enum]
}

View File

@ -81,4 +81,15 @@
\externalpage https://developer.android.com/reference/android/Manifest.permission#SYSTEM_ALERT_WINDOW
\title Android: SYSTEM_ALERT_WINDOW
*/
/*!
\externalpage https://developer.android.com/reference/android/app/Activity
\title Android: Activity
*/
/*!
\externalpage https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/
\title Kotlin: Int
*/
/*!
\externalpage https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/
\title Kotlin: String
*/