status-im / status-desktop

Status Desktop client made in Nim & QML
https://status.app
Mozilla Public License 2.0
287 stars 78 forks source link

SFPM fails to initialize properly in some specific scenarios #16310

Closed micieslak closed 4 days ago

micieslak commented 2 weeks ago

Bug Report

Description

In some specific cases combination of ConcatModel and SortFilterProxyModels fails to intiialize properly, ending up with a malformed model with narrowed set of roles (only roles defined as a proxy roles in SFPM).

The problem may be in SFPM and/or in ConcatModel

Changing

void QQmlSortFilterProxyModel::updateRoleNames()
{
    if (!sourceModel())
        return;

to

void QQmlSortFilterProxyModel::updateRoleNames()
{
    if (!sourceModel() || (sourceModel()->roleNames().empty() && sourceModel()->rowCount() == 0))
        return;

seems to solve the problem. However further investigation is needed to fully understand the problem. Also minimal example would be helpful there.

Steps to reproduce

On top of https://github.com/status-im/status-desktop/pull/16321:

--- a/ui/app/AppLayouts/Wallet/panels/SearchableAssetsPanel.qml
+++ b/ui/app/AppLayouts/Wallet/panels/SearchableAssetsPanel.qml
@@ -28,7 +28,7 @@ Control {
             balanceAsString     [string] - formatted balance per chain
             iconUrl             [url]    - chain's icon
     **/
-    property var model
+    property alias model: sfpm.sourceModel
     property string highlightedKey
     property string nonInteractiveKey

@@ -39,11 +39,6 @@ Control {
     SortFilterProxyModel {
         id: sfpm

-        // workaround for https://github.com/status-im/status-desktop/issues/16310
-        Component.onCompleted: {
-            sourceModel = Qt.binding(() => root.model)
-        }
-
         filters: AnyOf {
             SearchFilter {
                 roleName: "name"

SwapInputPanel storybook page becomes broken:

image

Expected behavior

Model initialized properly, with all expected roles.

Actual behavior

Model initialized improperly, only proxy roles of SFPM are reported via roleNames().

caybro commented 2 weeks ago

One possible place where this happens is the CommunityMembershipSetupDialog (namely the SharedAddressesAccountSelector); when joining/editing the addresses for our Status CC community, I see "no relevant tokens" (which is of course not true since I own the respective collectibles); I do have the STATCC collectible but it doesn't show up in the upper list:

image

micieslak commented 2 weeks ago

First example where SFPM fails to properly initialize role names because of the bug in workaround of QTBUG-57971. The roles in sfpm2 are not initialized are not initialized properly after source change in sfpm because the slot for was already &QQmlSortFilterProxyModel::initRoles triggered and disconnected:

import QtQuick 2.15
import QtQuick.Controls 2.15

import StatusQ.Core.Utils 0.1

import SortFilterProxyModel 0.2

Item {
    id: root

    ListModel {
        id: emptyModel
    }

    ListModel {
        id: emptyModel2
    }

    SortFilterProxyModel {
        id: sfpm

        sourceModel: emptyModel

        proxyRoles: ExpressionRole {
            name: "customRole"
            expression: "X" + model.index
        }
    }

    SortFilterProxyModel {
        id: sfpm2

        sourceModel: sfpm
    }

    ListView {
        id: lv

        anchors.fill: parent

        model: sfpm2

        delegate: Text {
            text: model.text + " | " + model.customRole
        }
    }

    Button {
        anchors.centerIn: parent

        text: "ADD"

        onClicked: {

            console.log(ModelUtils.roleNames(sfpm))
            console.log(ModelUtils.roleNames(sfpm2))

            emptyModel.append({ text: "d" })

            console.log(ModelUtils.roleNames(sfpm))
            console.log(ModelUtils.roleNames(sfpm2))

            sfpm.sourceModel = emptyModel2

            console.log(ModelUtils.roleNames(sfpm))
            console.log(ModelUtils.roleNames(sfpm2))

            emptyModel2.append({ text: "d" })

            console.log(ModelUtils.roleNames(sfpm))
            console.log(ModelUtils.roleNames(sfpm2)) // expected: [customRole,text]
        }
    }
}
micieslak commented 2 weeks ago

Second example showing improper roles initialization when models are initialized in a specific order. The order is forced by setting source in onCompleted handler, however the order of initialization is not specified and it's possible to encounter similar behavior when using standard wiring by bindings.

sfpm is set as source of sfpm2 in a deferred way, when it's proxy roles are already initialized. It blocks the mechanism of updating roles on insertion in sfpm2 because the condition for the source model is to have no roles (there is proxy role in our case). Because of that, when lm is populated, new roles are not reflected in sfpm2, leading to invalid rendering:

import QtQuick 2.15

import SortFilterProxyModel 0.2

Item {
    ListModel {
        id: lm
    }

    SortFilterProxyModel {
        id: sfpm

        sourceModel: lm

        proxyRoles: ExpressionRole {
            name: "sectionId"
            expression: "---"
        }
    }

    SortFilterProxyModel {
        id: sfpm2

        objectName: "spfm2"
    }

    ListView {
        anchors.fill: parent

        model: sfpm2

        delegate: Text {
            text: model.index + "/" + model.name
        }
    }

    Component.onCompleted: {
        sfpm2.sourceModel = sfpm

        lm.append({ name: "Aave" })
    }
}