Pull to refresh

For most people, lists in mobile apps have gone hand in hand with the pull to refresh for a while now.  Many of the biggest apps like Facebook, Twitter, among others make use of it to load feed data into a list.  Today I will be showing you how to add a pull to refresh in your own app using a listview!  To tell you the truth I didn't even know how to do this until a few weeks back when Jeremy Duke @BerryInformed gave me this little sample I will be using today to show you how to implement it.

Before we get started here are the files we will be making use of:

  • main.qml
  • ItemPage.qml (user created)
  • PullToRefresh.qml (user created)

Screenshots

 

 

 

PullToRefresh.qml

here is where we setup the actual visual part of the pull to refresh image that shows up at the top of a list

 
#include <bb/cascades/LocaleHandler>

 
 
import bb.cascades 1.0
Container {
    signal refreshTriggered
    property bool touchActive: false

    ImageView {
        id: imgRefreshIcon

        imageSource: "asset:///images/ic_refresh.png"
        horizontalAlignment: HorizontalAlignment.Center
        scalingMethod: ScalingMethod.AspectFit
        verticalAlignment: VerticalAlignment.Center
    }
    Label {
        id: lblRefresh
        text: "Pull down to refresh entries..."
        horizontalAlignment: HorizontalAlignment.Center
        verticalAlignment: VerticalAlignment.Center
        textStyle.textAlign: TextAlign.Center
    }

    attachedObjects: [
        LayoutUpdateHandler {
            id: refreshHandler
            onLayoutFrameChanged: {
                imgRefreshIcon.rotationZ = layoutFrame.y;
                if (layoutFrame.y >= 0.0) {
                    lblRefresh.text = "Release to refresh"

                    if (layoutFrame.y == 0 && touchActive != true) {
                        refreshTriggered();
                        lblRefresh.text = "Refreshing..."
                    }
                } else {
                    lblRefresh.text = "Pull down to refresh"
                }
            }
        }
    ]

}

as you see above we have placed a signal for when the pull to refresh animation should trigger the list to reload. We have also placed a property boolean so that this qml can access the touchActive information from the main.qml. We also have the refresh image icon and text that will be underneath it. Based on the position of the list the image will rotate. We have placed an if statement to change the animation text depending on the position. In essence it will say "Pull down to refresh" until you pull the list down enough in which case the text will change to "Release to refresh" and the image will be upside down. If the user lets go of the list the touchActive property will change and the combination of it and the release LayoutFrame position will signal "refreshTriggered" as well as change the text once again to "Refreshing..."

Main.qml

here is where we setup the list along with the datamodel that populates the list, how the list items look and the actions that occur when the list is touched (in order to get our PullToRefresh.qml animation stuff in the list). We've also gone ahead and added a ItemPage.qml to show an item's information on a pushed page for completeness, but this is optional.

 
import bb.cascades 1.2
import bb.data 1.0
NavigationPane {
    id: nav
    Page {
        Container {
            horizontalAlignment: HorizontalAlignment.Fill

            ListView {
                id: theList

                dataModel: dataModel
                property bool isTouched: false
                onTouch: {
                    if (event.isDown() | event.isMove()) {
                        isTouched = true
                    } else {
                        isTouched = false
                    }
                }
                leadingVisual: PullToRefresh {
                    id: pullRefresh
                    preferredWidth: listhandler.layoutFrame.width
                    touchActive: theList.isTouched
                    onRefreshTriggered: {

                        dataSource.load();

                    }
                }
                listItemComponents: [
                    ListItemComponent {
                        type: "item"
                        Container {
                            Container {
                                topPadding: 10
                                bottomPadding: 10
                                leftPadding: 15
                                rightPadding: 15
                                Label {
                                    text: ListItemData.title
                                    textStyle.base: SystemDefaults.TextStyles.TitleText
                                    multiline: true
                                }
                            }

                            Divider {

                            }

                        }
                    }
                ]

                onTriggered: {

                    if (indexPath.length > 1) {
                        var chosenItem = dataModel.data(indexPath);
                        var contentpage = itemPageDefinition.createObject();
                        contentpage.itemPageTitle = chosenItem.title
                        nav.push(contentpage);
                    }
                }

            }
            attachedObjects: [
                ComponentDefinition {
                    id: itemPageDefinition
                    source: "ItemPage.qml"
                },
                GroupDataModel {
                    id: dataModel
                },
                DataSource {
                    id: dataSource
                    source: "http://feeds.feedburner.com/blackberry/CAxx?format=xml"
                    remote: true
                    type: DataSourceType.Xml
                    query: "/rss/channel/item"
                    onDataLoaded: {
                        dataModel.clear()
                        dataModel.insertList(data)
                        theList.scrollToPosition(ScrollPosition.Beginning, ScrollAnimation.Smooth);
                    }
                },
                LayoutUpdateHandler {
                    id: listhandler
                    onLayoutFrameChanged: {
                        console.log(layoutFrame.width)
                    }
                }
            ]
            onCreationCompleted: {
                dataSource.load()
            }
        }
    }

    onPopTransitionEnded: {
        page.destroy();
    }
}

here is where we setup the list along with the datamodel that populates the list, how the list items look and the actions that occur when the list is touched (in order to get our PullToRefresh.qml animation stuff in the list). We've also gone ahead and added a ItemPage.qml to show an item's information on a pushed page for completeness, but this is optional.

The following code is the setup the DataSource which contains an .xml feed for our GroupDataModel which gets used by our ListView to display the contents of the RSS feed

 
 
ListView {
                id: theList

                dataModel: dataModel
                listItemComponents: [
                    ListItemComponent {
                        type: "item"
                        Container {
                            Container {
                                topPadding: 10
                                bottomPadding: 10
                                leftPadding: 15
                                rightPadding: 15
                                Label {
                                    text: ListItemData.title
                                    textStyle.base: SystemDefaults.TextStyles.TitleText
                                    multiline: true
                                }
                            }

                            Divider {

                            }

                        }
                    }
                ]

                onTriggered: {

                    if (indexPath.length > 1) {
                        var chosenItem = dataModel.data(indexPath);
                        var contentpage = itemPageDefinition.createObject();
                        contentpage.itemPageTitle = chosenItem.title
                        nav.push(contentpage);
                    }
                }

            }


As you can see, you create a ListView item and then proceed to define the datamodel as well as the ListItemComponent which is to say, how each list item looks. In this example we defined the listitemcomponent as an "item" and adjusted how the text looks as well as defined what the text is. For your reference, you can setup a listitem header by creating another listitemcomponent with type: "header". In the code above, we also define what happens when a user triggers and item in the list. Here we have it set to open the ItemPage.qml that will be defined in the following code below, and show what data we want to get passed to it.

Right below we have our attached objects that define the DataSource, groupdatamodel and define the ItemPage.qml for when it's called on the listview onTriggered

 
 
attachedObjects: [
                ComponentDefinition {
                    id: itemPageDefinition
                    source: "ItemPage.qml"
                },
                GroupDataModel {
                    id: dataModel
                },
                DataSource {
                    id: dataSource
                    source: "http://feeds.feedburner.com/blackberry/CAxx?format=xml"
                    remote: true
                    type: DataSourceType.Xml
                    query: "/rss/channel/item"
                    onDataLoaded: {
                        dataModel.clear()
                        dataModel.insertList(data)
                        theList.scrollToPosition(ScrollPosition.Beginning, ScrollAnimation.Smooth);
                    }
                },
                LayoutUpdateHandler {
                    id: listhandler
                    onLayoutFrameChanged: {
                        console.log(layoutFrame.width)
                    }
                }
            ]

Now here is where the important part comes in for the Pull to Refresh aspect of this sample. The code below is placed within the ListView. We create a propert boolean "isTouched" so that we can use it to change values for our desired effect. We setup a onTouch where if the list is being touched our boolean "isTouched" becomes true. We setup a leadingVisual which uses the PullToRefresh.qml (all that stuff we setup earlier). here we define touchActive as the "isTouched" boolean. When the touchActive turns true (based on the isTouched boolean) it sends the onRefreshTriggered signal where we've setup the dataSource.load() to load the dataSource.

 
                property bool isTouched: false
onTouch: {
                    if (event.isDown() | event.isMove()) {
                        isTouched = true
                    } else {
                        isTouched = false
                    }
                }
                leadingVisual: PullToRefresh {
                    id: pullRefresh
                    preferredWidth: listhandler.layoutFrame.width
                    touchActive: theList.isTouched
                    onRefreshTriggered: {

                        dataSource.load();

                    }
                }

Wow that tutorial was a mouthfull! You should now have a simple Pull To Refresh that you can modify and change based on your needs. If you have any questions don't hesitate to leave a comment or send me a tweet at @ElBranduco and don't forget to thank Jeremy Duke at @BerryInformed for the sample! Hope you guys enjoy ;)

Brandon Orr

Transportation Planner (University of Waterloo Graduate) & Blackberry 10 Developer (PinGuin App) part of the open source BB team.

Website: appworld.blackberry.com/webstore/vendor/65375/

Leave your comments

Post comment as a guest

0

People in this conversation

  • Guest (Joe)

    how would i implement this with a webView Scrollable eee,f

    Like 0 Short URL:
Subscribe to the official OSBB BBM Channel!

osbbchannelQR

C00013E89

Back to top