Building an Ubuntu SDK App: rev 3

This is part 3 of an ongoing series, you should read rev 1 and rev 2 first.

In this revision I make several visual improvements to the existing components, try out some new gesture-based interactions, and undergo a significant refactoring effort to separate my code into smaller, cleaner files.

The Refactor

For the refactor, I wanted to split my app into logical components, based largely on the QML Components, but grouping the major and minor components that could be treated as a single entity.

I started by separating the components for each of my Pages, subreddits and articleView, into independent QML files that I could treat as single components when adding them to my Page.  For the SubredditListView, I further separated the model code (based on the JSONListModel) and delegate code (based on ListItem.Subtitled) into their own files.

These changes would allow me build domain-specific functionality on top of the base components in the Ubuntu SDK, while keeping my main code file uncluttered by all of that code.  My main file, uReadIt.qml, could then focus solely on layout and navigation.

Connecting the dots

I went out of my way to avoid inter-dependency between these components, so the ArticleListItem doesn’t need to know about the ArticleView.  But I wanted to change my ArticleView whenever an ArticleListItem was clicked.  This meant I had provide aliases, signals and callback handlers on my top-level components, and they connect them together in my main file.

I gave my SubredditListView an itemClicked signal, which would automatically provide an onItemClicked callback property that I could access from uReadIt.qml.  Then, in my delegate’s onClicked callback, I simply fired off the signal with a reference to the ListModel item.

Item {
    ...
    signal itemClicked(var model)
    ...
    ListView {
        id: articleList
        ...
        delegate: ArticleListItem {
            id: articleItemDelegate
            onClicked: {
                itemClicked(model)
            }
        }
        ...
    }
    ...
}

Then in my ArticleView code, I made a property alias called url that was linked to the url property on the inner WebView component.  Setting ArticleView.url would then behave exactly like setting WebView.url did.

Item {
    property alias url: articleWebView.url
    ...
    WebView {
        id: articleWebView
        url: ""
        ...
    }
    ...
}

Finally, in uReadIt.qml, I set the onItemClicked handler for my SubredditListView to change the url property on my ArticleView,

    PageStack {
        id: pageStack
        ...
        Page {
            id: subreddits
            ...
            SubredditListView {
                id: articleList
                ...
                onItemClicked: {
                    articleView.title = model.data.title
                    articleContent.url = model.data.url
                    articleContent.visible = true
                    pageStack.push(articleView)
                }
            }
            ...
        }
        Page {
            id: articleView
            title: 'Article'

            ArticleView {
                id: articleContent
                ...
            }
        }
        ...
    }
}

Visual tweaks

Alright, enough of the refactoring, I managed to do some more interesting and fun things in this revision as well.  For one thing, I improved the look of thumbnails on the ListView by giving different icons for in-Reddit articles, as well as NSFW and ‘default’ articles.  I also restricted their size to 5×5 grid units.

Grid Unit is a resolution-independent way of defining size of things in the Ubuntu SDK.  Instead of using pixels, which don’t work on both high and low density displays, or using physical units which don’t work on both hand-held and 10-foot displays, the Ubuntu SDK uses a Grid Unit.  The number of pixels in a grid unit depends on the device your app is running on.  On high-density displays, like the Retina displays on new Macs, your grid unit will use more pixels than on a standard resolution LCD, so that a Grid Unit is roughly the same physical size on both.  Likewise, on a television screen meant to be viewed from across the room, a grid unity will have a larger physical size than it would when running on a hand-held device, even if they are both 1080p screens.

ListItem.Subtitled {
    text: model.data.title
    subText: '('+model.data.domain+') - ' + model.data.score + ' - ' + model.data.subreddit + ' - ' + model.data.author
    icon: {
        var icon = model.data.thumbnail;
        if (icon == 'self') {
            icon = Qt.resolvedUrl("reddit.png");
        } else if (icon == 'default') {
            icon = Qt.resolvedUrl("avatar.png");
        } else if (icon == 'nsfw') {
            icon = Qt.resolvedUrl("settings.png");
        }

        return icon;
    }
    __iconHeight: units.gu(5)
    __iconWidth: units.gu(5)
    progression: true
}

In addition to these changes to the ListView, I was also getting tired of wondering if my content was being slow to load, or if it had failed for some reason, so I wanted to add a loading progress bar to my ArticleView.

To do this, I used the ProgressBar component from the Ubuntu SDK, and connected it to the loading property for the WebView component.  First I set the visibility of the progress bar to the loading status of the content with the onLoadingChanged callback.  If it was loading, the bar was visible, and when it wasn’t the bar was hidden.  Next I used the onLoadProgressChanged to set the progress bar’s value to the current loading progress of the content.  Once everything was connected, QML made it all just work.

    WebView {
        id: articleWebView
        ...
        onLoadingChanged: {
            loadProgressBar.visible = loading
        }

        onLoadProgressChanged: {
            loadProgressBar.value = loadProgress
        }
    }
    ProgressBar {
        id: loadProgressBar
        ...
        minimumValue: 0
        maximumValue: 100
    }

Dragging gestures

Finally I started to experiment with drag-gestures for moving from one page of results to the next, or reloading the subreddit entirely.  This was pretty tricky, the ListView component doesn’t provide any single property to tell you how far past the either end a user drag or flick has moved the content.  However, it does provide a contentY property that I could use to, eventually, calculate how far off the natural bounds the user has moved the content.

First I created a callback handler for onContentYChanged so that my app was aware of the content movement within the ListView.  Then, if Qt says the user was dragging the content (as opposed to movement caused by a flick), I would calculate the over-drag for both the top and bottom of the list.  I didn’t want to trigger an event for small drag distances, so below a certain threshold it will give instructions to continue dragging to perform an action, and beyond that threshold the text will change to tell the user to let go of the drag to initiate that action.

Next time: Packaging

By now I had an app that I wanted to use regularly on my Nexus 7.  Previously I had been running it from QtCreator by pressing Ctrl+F12 while I had my N7 connected via a USB cable, but that meant I could only start it when I was plugged into my laptop.  Not ideal for in-bed Reddit browsing.  So in the 4th revision of my code branch I added Debian packaging files for easy installation.

Read the next article

This entry was posted in OpenSource, Programming, Projects and tagged , , , , , , . Bookmark the permalink.

5 Responses to Building an Ubuntu SDK App: rev 3

  1. sil says:

    Why
    WebView {
    onLoadingChanged: {
    loadProgressBar.visible = loading
    }
    onLoadProgressChanged: {
    loadProgressBar.value = loadProgress
    }
    }

    rather than

    ProgressBar { visible: webview.loading; value: webview.progress } ?

    • Michael Hall says:

      Ignorance mostly, this is my first ever QML application and I’m not used to all the magic yet, so there’s going to be a lot of things done like I would in python/gtk or java/swing.

  2. Olivier says:

    Quick tip for the progress bar: instead of implementing signal handlers (onLoadingChanged and onLoadProgressChanged), just bind the properties:

    ProgressBar {
    value: webview.loadProgress
    visible: webview.loading
    }

  3. Pingback: Building an Ubuntu SDK App: rev 2 | Michael Hall

Comments are closed.