Sunday, March 30, 2014

Using JSON requests in QML

Together with a friend I'm currently taking part in the Ubuntu App Showdown. We decided to implement a simple QML Pocket client. However, the pocket API is JSON based while the Ubuntu SDK (which is based on QML/QT) just provides means to work with XML HTTP requests. Here is how to work with JSON requests in QML.

To leverage working with JSON requests in QML, kromain implemented a simple ListModel based way to handle JSON requests and responses. It works great for GET requests, but doesn't support POST requests and also misses some other commonly used features like setting the content type header. Therefore we extended his implementation with POST support and some other neat features. As of now he didn't merge our pull request, but you can of course use the code directly off from the patch branch in my repository.

I will walk you through how to set up basic JSON based request/response handling using the extended version of kromain's QML. There are two approaches. Which one you choose depends on how you want to use the response data.

Using response data in a ListView

If you want to use the response data in a QML ListView you need to provide the JSONListModel as a model for your list view like outlined in the following example which shows how to implement a simple GET request for the unread objects on a user's pocket list (I will not bother with how you can acquire the consumer key and the access token as this is depends heavily on which service you plan to use):

import QtQuick 2.0
import "../json" // this folder contains JSONListModel.qml and jsonpath.js


ListView {
    id: items

    property string accessToken: ""

    model: ListModel {}
    
    JSONListModel { 
       id: pocketModel
       source: "https://getpocket.com/v3/get?consumer_key=YOUR_CONSUMER_KEY&access_token=" + accessToken
       query: "$.list.*" // simple XPATH query over the JSON result
       requestType: "GET" // this is optional as GET is default

       Component.onCompleted: {
           items.model = model; // assign JSON model to ListView model (this only happens once when the component is created)
       }
    }

    delegate: Subtitled {
        text: resolved_title
        subText: excerpt

    }

    onAccessTokenChanged: {
        if (accessToken !== "") {
            refresh();
        }
    }

    function refresh() {
        pocketModel.refresh();
    }
}

This code will trigger a request for the user's reading list as soon as the access token is set. As soon as the model has changed the ListView's content will adapt automatically.

Using the response as-is

If you want to handle the response yourself you simply omit adding the model to a ListView. In the following example we shorten a URL using the Google URL Shortener JSON API and post the shortened URL to the clipboard. At first we create a ShortenRequest component for better reusability.

import QtQuick 2.0
import "../json"

JSONListModel {
    property string longUrl: ""

    source: "https://www.googleapis.com/urlshortener/v1/url"
    contentType: "application/json"
    requestType: "POST"
    query: "$"
    postData: "{\"longUrl\":\"" + longUrl + "\"}"
 

    onListModelUpdateChanged: {
        var shortUrl = model.get(0).id;
        Clipboard.push(shortUrl);
    }
}

When we want to send the request we just have to call the following method which creates a new instance of our ShortenRequest:

function shorten(longUrl) {
    var request = Qt.createComponent("path/to/ShortenRequest.qml").createObject(null, {"longUrl": longUrl};
    request.refresh();
}