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:
parent
7afacee589
commit
50aea1f6a3
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
|
@ -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
|
||||||
|
*/
|
|
@ -3,19 +3,23 @@
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
|
||||||
|
//! [QML root item and dataModel definition]
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: mainRectangle
|
id: mainRectangle
|
||||||
|
|
||||||
property AbstractItemModel dataModel
|
property AbstractItemModel dataModel
|
||||||
|
//! [QML root item and dataModel definition]
|
||||||
|
|
||||||
color: "#00414A"
|
color: "#00414A"
|
||||||
border.width: 2
|
border.width: 2
|
||||||
border.color: "black"
|
border.color: "black"
|
||||||
|
|
||||||
|
//! [ListView definition]
|
||||||
ListView {
|
ListView {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
model: mainRectangle.dataModel
|
model: mainRectangle.dataModel
|
||||||
|
//! [ListView definition]
|
||||||
ScrollBar.vertical: ScrollBar {}
|
ScrollBar.vertical: ScrollBar {}
|
||||||
spacing: 10
|
spacing: 10
|
||||||
|
|
||||||
|
@ -24,6 +28,7 @@ Rectangle {
|
||||||
margins: 20
|
margins: 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! [ListView delegate definition]
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
required property var model
|
required property var model
|
||||||
|
|
||||||
|
@ -65,5 +70,6 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//! [ListView delegate definition]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,13 @@ plugins {
|
||||||
id 'org.qtproject.qt.gradleplugin' version '0.1-SNAPSHOT+'
|
id 'org.qtproject.qt.gradleplugin' version '0.1-SNAPSHOT+'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! [build.gradle QtBuild config]
|
||||||
QtBuild {
|
QtBuild {
|
||||||
// Relative for pre-installed (Installer or MaintenanceTool) installations.
|
// Relative for pre-installed (Installer or MaintenanceTool) installations.
|
||||||
qtPath file('../../../../../../../6.8.0')
|
qtPath file('../../../../../../../6.8.0')
|
||||||
projectPath file('../../qtabstractlistmodel')
|
projectPath file('../../qtabstractlistmodel')
|
||||||
}
|
}
|
||||||
|
//! [build.gradle QtBuild config]
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'com.example.qtabstractlistmodel_kotlin'
|
namespace 'com.example.qtabstractlistmodel_kotlin'
|
||||||
|
|
|
@ -14,23 +14,25 @@ import org.qtproject.qt.android.QtQmlStatus
|
||||||
import org.qtproject.qt.android.QtQmlStatusChangeListener
|
import org.qtproject.qt.android.QtQmlStatusChangeListener
|
||||||
import org.qtproject.qt.android.QtQuickView
|
import org.qtproject.qt.android.QtQuickView
|
||||||
|
|
||||||
|
//! [MainActivity definition]
|
||||||
class MainActivity : AppCompatActivity(), QtQmlStatusChangeListener {
|
class MainActivity : AppCompatActivity(), QtQmlStatusChangeListener {
|
||||||
private val m_mainQmlComponent: Main = Main()
|
private val m_mainQmlComponent: Main = Main()
|
||||||
private val m_listModel = MyListModel()
|
private val m_listModel = MyListModel()
|
||||||
|
//! [MainActivity definition]
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
//! [Adding QtQuickView]
|
||||||
val qtQuickView: QtQuickView = QtQuickView(this)
|
val qtQuickView: QtQuickView = QtQuickView(this)
|
||||||
m_mainQmlComponent.setStatusChangeListener(this)
|
|
||||||
|
|
||||||
val params: ViewGroup.LayoutParams = FrameLayout.LayoutParams(
|
val params: ViewGroup.LayoutParams = FrameLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
)
|
)
|
||||||
val qmlFrameLayout: FrameLayout = findViewById<FrameLayout>(R.id.qmlFrame)
|
val qmlFrameLayout: FrameLayout = findViewById<FrameLayout>(R.id.qmlFrame)
|
||||||
qmlFrameLayout.addView(qtQuickView, params)
|
qmlFrameLayout.addView(qtQuickView, params)
|
||||||
|
//! [Adding QtQuickView]
|
||||||
|
//! [Adding control buttons]
|
||||||
val addRowAtEndButton: Button = findViewById<Button>(R.id.addRow)
|
val addRowAtEndButton: Button = findViewById<Button>(R.id.addRow)
|
||||||
val removeRowFromEndButton: Button = findViewById<Button>(R.id.removeRow)
|
val removeRowFromEndButton: Button = findViewById<Button>(R.id.removeRow)
|
||||||
addRowAtEndButton.setOnClickListener { _: View? ->
|
addRowAtEndButton.setOnClickListener { _: View? ->
|
||||||
|
@ -39,13 +41,18 @@ class MainActivity : AppCompatActivity(), QtQmlStatusChangeListener {
|
||||||
removeRowFromEndButton.setOnClickListener { _: View? ->
|
removeRowFromEndButton.setOnClickListener { _: View? ->
|
||||||
m_listModel.removeRow()
|
m_listModel.removeRow()
|
||||||
}
|
}
|
||||||
|
//! [Adding control buttons]
|
||||||
|
//! [Loading the QML component]
|
||||||
|
m_mainQmlComponent.setStatusChangeListener(this)
|
||||||
qtQuickView.loadComponent(m_mainQmlComponent)
|
qtQuickView.loadComponent(m_mainQmlComponent)
|
||||||
|
//! [Loading the QML component]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! [Linking the data model]
|
||||||
override fun onStatusChanged(qtQmlStatus: QtQmlStatus) {
|
override fun onStatusChanged(qtQmlStatus: QtQmlStatus) {
|
||||||
if (qtQmlStatus === QtQmlStatus.READY) {
|
if (qtQmlStatus === QtQmlStatus.READY) {
|
||||||
m_mainQmlComponent.setDataModel(m_listModel)
|
m_mainQmlComponent.setDataModel(m_listModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//! [Linking the data model]
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,66 @@ import java.util.UUID
|
||||||
import org.qtproject.qt.android.QtAbstractListModel
|
import org.qtproject.qt.android.QtAbstractListModel
|
||||||
import org.qtproject.qt.android.QtModelIndex
|
import org.qtproject.qt.android.QtModelIndex
|
||||||
|
|
||||||
|
//! [MyListModel definition]
|
||||||
class MyListModel : QtAbstractListModel() {
|
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) {
|
private enum class DataRole(val m_value: Int) {
|
||||||
UUID(0),
|
UUID(0),
|
||||||
Row(1);
|
Row(1);
|
||||||
|
@ -24,52 +83,5 @@ class MyListModel : QtAbstractListModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//! [MyListModel::DataRole enum]
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,4 +81,15 @@
|
||||||
\externalpage https://developer.android.com/reference/android/Manifest.permission#SYSTEM_ALERT_WINDOW
|
\externalpage https://developer.android.com/reference/android/Manifest.permission#SYSTEM_ALERT_WINDOW
|
||||||
\title Android: 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
|
||||||
|
*/
|
||||||
|
|
Loading…
Reference in New Issue