Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Native Development

Reply
Developer
greenback
Posts: 492
Registered: ‎10-17-2010
My Device: BlackBerry Z10, DAC
Accepted Solution

Method for making an HTTP style request in Cascades/QML?

Something like AJAX. What's the equivalent in Cascades/QML? What classes should I explore in the API?

Guidance would be greatly appreciated.

 

The requests are variable.

 

http://someservice.com/v1/do/blah

http://someservice.com/v1/do/meh

 

The output from the service is JSON.

I will take the output and probably put it into a list (much like the Stamp Collector sample app)

 

Any help or sample code to get me started would really help. I imagine this would be useful for other developers who integrate third-party APIs or data into their BB10 Cascades app.

 

Thanks!

Please use plain text.
Developer
peter9477
Posts: 6,473
Registered: ‎12-08-2010
My Device: PlayBook, Z10
My Carrier: none

Re: Method for making an HTTP style request in Cascades/QML?

The equivalent in QML is the same standard XMLHttpRequest object: http://qt-project.org/doc/qt-4.8/qdeclarativeglobalobject.html

I don't know if that's available under Cascades, though as it's in the Javascript and not the QML per se, I would expect it to be.

Peter Hansen -- (BB10 and dev-related blog posts at http://peterhansen.ca.)
Author of White Noise and Battery Guru for BB10 and for PlayBook | Get more from your battery!
Please use plain text.
Developer
greenback
Posts: 492
Registered: ‎10-17-2010
My Device: BlackBerry Z10, DAC

Re: Method for making an HTTP style request in Cascades/QML?

This doesn't work :smileysad:

 

 

Please use plain text.
New Contributor
Luqma_
Posts: 2
Registered: ‎06-11-2012
My Device: Blackberry Curve 9360
My Carrier: Wind Mobile

Re: Method for making an HTTP style request in Cascades/QML?

Here is a simple sample to get you going. (Albeit it's not only QML but C++).

 

http://codepad.org/u7IsmiWT

 

It uses QNetworkAccessManager for the actual HTTP request and the cascades specific JsonDataAccess to parse JSON.

Please use plain text.
Trusted Contributor
Brennan12325
Posts: 204
Registered: ‎05-15-2012
My Device: None
My Carrier: Telus

Re: Method for making an HTTP style request in Cascades/QML?

If you're parsing complex json JsonDataAccess turns out to be a poor solution (or at least I couldn't make it do anything special). You can get a little more bang for your buck if you use QScriptValue, QScriptEngine, and QScriptIterator.

 

Example:

 

void App::replyFinished(QNetworkReply* reply)
{
	qDebug() << "Got into replyFinished";

	if (reply->error() == QNetworkReply::NoError)
    {
		QByteArray result = reply->readAll();
		QScriptValue sc;
		QScriptEngine engine;
		sc = engine.evaluate("(" + QString(result) + ")");

		if (sc.property("root").property("children").isArray())
		{
			 QScriptValueIterator it(sc.property("data").property("children"));
			 while (it.hasNext()) {
				 it.next();

				 qDebug("Name %s", it.name().toStdString().c_str());
}
}
        }
 }

 would be able to parse the name element out of JSON data in this format:

 

{
   "id":"9887",
   "root":{
      "children":[
         {
             "name":"name_1"
         }
         },
         {
             "name":"name_2"              
         }
     ],
    "moreData":"afterTheRootsChildren"
}

 

 

----------------------
Check out my app, Alien Flow for reddit

And of course, like my post if you found it helpful or informative!
Please use plain text.
New Contributor
Luqma_
Posts: 2
Registered: ‎06-11-2012
My Device: Blackberry Curve 9360
My Carrier: Wind Mobile

Re: Method for making an HTTP style request in Cascades/QML?

What do you mean by make it do anything special? To use the example you gave, that can be done with JsonDataAccess and in a much clearer way (my opinion of course).

 

See:

void App::handleNetworkData(QNetworkReply *reply)
{

    if (!reply->error()) {
    
        // Let's get ALL the data
        const QByteArray response(reply->readAll());

        // and parse the JSON into a usable format
        JsonDataAccess jda;
        QVariantMap results = jda.loadFromBuffer(response).toMap();

        // Get the relevant parts we want from the JSON
        QVariantList children = results["root"].toMap()["children"].toList();
Q_FOREACH(QVariant child, children) {
qDebug("Name %s", child.toMap()["name"].toString().toStdString().c_str());
} } // Cleanup reply->deleteLater(); }

 

Please use plain text.
Trusted Contributor
Brennan12325
Posts: 204
Registered: ‎05-15-2012
My Device: None
My Carrier: Telus

Re: Method for making an HTTP style request in Cascades/QML?

I couldn't find any documentation on parsing a JSON array that wasn't in a very simple format (where the list of relevant data wasn't at the root. All the examples I found had the relevant array of data at the root element).

 

Like there example in the docs:

contacts.json file contains: [ { "id":1, "firstname": "Abby", "lastname": "Stevens", "title": "Sr. Editor", "image": "images/data/abby_stevens.png", "active": true, "sex": "f" }, { "id":2, "firstname": "Allison", "lastname": "Lo", "title": "Talent Scout", "image": "images/data/allison_lo.png", "active": true, "sex": "f" }, ... ]

 

Where they simply parse like this:

JsonDataAccess jda("contacts.json");
QVariant list = jda.load();

 

It appears the functions I was looking for were toMap and toList.

 

I'm not sure your method is any clearer. More elegent perhaps. It is certainly what I was looking for when I tried to solve the problem with JsonDataAccess : )

----------------------
Check out my app, Alien Flow for reddit

And of course, like my post if you found it helpful or informative!
Please use plain text.
Developer
greenback
Posts: 492
Registered: ‎10-17-2010
My Device: BlackBerry Z10, DAC

Re: Method for making an HTTP style request in Cascades/QML?

[ Edited ]

Thank you for contributing sample code. I am still confused on how to put everything together. From starting a new project, to where to put these classes and what many of these things do.

 

Putting it all together is tricky, especially for n00bs like me who want to create compelling applications.

I have been reading the docs, and going through sample tutorials for the past couple of days.
 

I will take the next day or two to see if I can get a simple web service application working.

Once I figure this out, I will post detailed instructions in this thread to help other developers who may be interested in creating their own web service enabled/connected application.

Gettings Set up

  • Creating a new project in Cascades 4
  • Connecting c++ code to qml, specifically the web service data
  • What each part of the http service call is and means (in the context of BlackBerry apps)
  • Handling output, esp for APIs. If you know the expected values, maybe there is a way to model the data uniformly
  • Outputing to the qml view
  • etc etc.

This will help everyone in the community. Pushing data channels into BB10 apps is critical!

 

Thanks. Be back with my code once I figure it out. Feel free to further contribute.

:Beta1: 

 

Please use plain text.
BlackBerry Development Advisor (Retired)
selom
Posts: 60
Registered: ‎05-10-2012
My Device: Blackberry 10 Alpha
My Carrier: none

Re: Method for making an HTTP style request in Cascades/QML?

Hi,

 

I wrote a simple sample application to demonstrate how to create an application that consumes a twitter feed and display the parsed JSON content in a standard list view.

 

Begin by creating a new project named "Twitter", by selecting File->New->BlackBerry Cascades C++ Project, and choosing the "Standard Empty Project" option.

 

We will start off by creating a class called TwitterRequest responsible for downloading and notifying us through slots and signals that the twitter JSON data is available. This class should be put in your /src folder of the project

 

TwitterRequest.hpp

 

/*
 * Copyright (c) 2011-2012 Research In Motion Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef TWITTERREQUEST_HPP_
#define TWITTERREQUEST_HPP_

#include <QtCore/QObject>

/*
 * This class is responsible for making a REST call to the twitter api
 * to retrieve the latest feed for a twitter screen name. It emits the complete()
 * signal when the request has completed.
 */
class TwitterRequest : public QObject
{
    Q_OBJECT
public:
    TwitterRequest();
    virtual ~TwitterRequest();

    /*
     * Makes a network call to retrieve the twitter feed for the specified screenname
     * @param screenname - the screen name of the feed to extract
     * @see onTimelineReply
     */
    void getTimeline(QString screenname);

public slots:
    /*
     * Callback handler for QNetworkReply finished() signal
     */
    void onTimelineReply();

signals:
    /*
     * This signal is emitted when the twitter request is received
     * @param info - on success, this is the json reply from the request
     *               on failure, it is an error string
     * @param success - true if twitter request succeed, false if not
     */
    void complete(QString info, bool success);
};

#endif /* TWITTERREQUEST_HPP_ */

 

TwitterRequest.cpp

 

/*
 * Copyright (c) 2011-2012 Research In Motion Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "TwitterRequest.hpp"
#include <QNetworkAccessManager>
#include <QUrl>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QDebug>

TwitterRequest::TwitterRequest()
{
}

TwitterRequest::~TwitterRequest()
{
}

void TwitterRequest::getTimeline(QString screenname)
{
    QNetworkAccessManager* netManager = new QNetworkAccessManager();
    if (!netManager)
    {
        qDebug() << "Unable to create QNetworkAccessManager!";
        emit complete("Unable to create QNetworkAccessManager!", false);
        return;
    }

    QString queryUri = "http://api.twitter.com/1/statuses/user_timeline.json?include_entities=true&include_rts=true&screen_n...";
    queryUri += screenname;
    QUrl url(queryUri);
    QNetworkRequest req(url);

    QNetworkReply* ipReply = netManager->get(req);
    connect(ipReply, SIGNAL(finished()), this, SLOT(onTimelineReply()));
}

void TwitterRequest::onTimelineReply()
{
    QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
    QString response;
    bool success = false;
    if (reply)
    {
        if (reply->error() == QNetworkReply::NoError)
        {
            int available = reply->bytesAvailable();
            if (available > 0)
            {
                int bufSize = sizeof(char) * available + sizeof(char);
                QByteArray buffer(bufSize, 0);
                int read = reply->read(buffer.data(), available);
                response = QString(buffer);
                success = true;
            }
        }
        else
        {
            response =  QString("Error: ") + reply->errorString() + QString(" status:") + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString();
            qDebug() << response;
        }
        reply->deleteLater();
    }
    if (response.trimmed().isEmpty())
    {
        response = "Twitter request failed. Check internet connection";
        qDebug() << response;
    }
    emit complete(response, success);
}

 

 

Next, replace the main.qml with the content below.

 

import bb.cascades 1.0

Page {
    content: Container {
        background : Color.DarkRed
        layout : DockLayout {   
        }
        ListView {
            layoutProperties : DockLayoutProperties {
                verticalAlignment : VerticalAlignment.Center
            } 
            objectName : "basicTimelineView"
            id : basicTimelineView
            listItemComponents: [
                ListItemComponent {
                    type: "item"				 
                    StandardListItem {
                        statusText: {
                            ListItemData.created_at
                        }
                        descriptionText: {
                            ListItemData.text
                            }				            
                    }
                }
            ]
        }
    }
    onCreationCompleted: {
        cs.getTimeline("ladygaga");
    }
}

 

This is a simple page with a ListView with standard components showing the date and content of the tweet. As you can see, immediately after the page is created a call is made into the c++ code using the "cs" context property set in the App constructor to retrieve the recent tweets of the usernamed "ladygaga". You can learn more about calling c++ from QML here https://bdsc.webapps.blackberry.com/cascades/documentation/ui/integrating_cpp_qml/index.html

 

Finally, tying all of this together is the App class. It uses slots to handle the "complete" signal generated by the TwitterRequest class when the data is available, parses the data into a GroupData model and populates the ListView with the retrieved data.

 

App.hpp

 

#ifndef APP_H
#define APP_H

#include <QtCore/QObject>
#include <bb/cascades/AbstractPane>

class App : public QObject
{
    Q_OBJECT

public:
    App();

    /*
     * Called by the QML to get a twitter feed for the screen nane
     */
    Q_INVOKABLE void getTimeline(QString screenName);

public slots:
    /*
     * Handles the complete signal from TwitterRequest when
     * the request is complete
     * @see TwitterRequest::complete()
     */
    void onTwitterTimeline(QString info, bool success);

protected:
    bb::cascades::AbstractPane* m_root;
};

#endif // ifndef APP_H

 

App.cpp

 

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/GroupDataModel>
#include <bb/cascades/ListView>
#include <bb/data/jsondataaccess>

#include "App.hpp"
#include "TwitterRequest.hpp"

using namespace bb::cascades;

App::App()
{
    QmlDocument *qml = QmlDocument::create("main.qml");
    qml->setContextProperty("cs", this);

    m_root = qml->createRootNode<AbstractPane>();
    Application::setScene(m_root);
}

void App::getTimeline(QString screenName)
{
    //sanitize screenname
    QStringList list = screenName.split(QRegExp("\\s+"), QString::SkipEmptyParts);
    if (list.count() <= 0)
    {
        qDebug() << "please enter a valid screen name";
        return;
    }
    QString twitterid = list[0];

    TwitterRequest* tr = new TwitterRequest();
    tr->getTimeline(twitterid);
    connect(tr, SIGNAL(complete(QString, bool)), this, SLOT(onTwitterTimeline(QString, bool)));
}

void App::onTwitterTimeline(QString info, bool success)
{
    if (!success)
    {
    	qDebug() << "Error retrieving twitter fee: " << info;
    	return;
    }

    ListView* list = m_root->findChild<ListView*>("basicTimelineView");
    if (!list || list->dataModel() != NULL)
    {
        qDebug() << "basic list already populated";
        return; //if basic timeline list not found or already populated do nothing
    }

    // Create a group data model with id as the sorting key
    GroupDataModel* dm = new GroupDataModel(QStringList() << "id_str");
    dm->setGrouping(ItemGrouping::None);

    // parse the json response with JsonDataAccess
    bb::data::JsonDataAccess ja;
    QVariant jsonva = ja.loadFromBuffer(info);

    // the qvariant is an array of tweets which is extracted as a list
    QVariantList feed = jsonva.toList();

    // for each object in the array, push the variantmap in its raw form
    // into the ListView
    for (QList<QVariant>::iterator it = feed.begin(); it != feed.end(); it++)
    {
        QVariantMap tweet = it->toMap();
        dm->insert(tweet);
    }

    // set the data model to display
    list->setDataModel(dm);
    list->setVisible(true);
}

 

I hope this is enough to get you started. There are lots of enhancements that can be done, for example using multiple pages, loading more tweets ajax style, even having a page where the twitter username can be changed, error handling and so on. Good Luck!

 

Cheers

Selom

Please use plain text.
New Contributor
lilin
Posts: 9
Registered: ‎06-14-2012
My Device: Alpha device
My Carrier: B.

Re: Method for making an HTTP style request in Cascades/QML?

Hi Selom,

 

Could you please post this example on github to keep track of it ?

(in Cascades-Samples  or Cascades-Community-Samples)

 

Thanks :smileyhappy:

Please use plain text.