I'm working in Blackberry 10 Cascades, QML and C++ QT, and I am trying to get some XML data from a small php webservice I wrote, to load into the list on the Blackberry 10 Dev Alpha Simulator I'm using - but it's not working.
That is the xml data does not load into the ListView of QML document, to show on the screen of the Blackberry Simulator. I need help to get that work.
I had started with an example that involved a regular http request, and modified it to make it customized to my purposes (which is a http post request). (This code takes a generation number (1-5) in the text field) and prints out a list of the colours of the pokemon games from that generation).
This is the QML file I started with:
import bb.cascades 1.0
TabbedPane {
activePane: Page {
actions: [
// An action item that calls the C++ function that retrieves
// the contact list
ActionItem {
title: "Refresh"
onTriggered: app.initiateRequest()
}
]
content: Container {
layout: DockLayout {}
// A list that has two list item components, one for a header
// and one for contact names. The list has an object name so
// that we can set the data model from C++.
ListView {
objectName: "list"
layout: FlowListLayout {
topPadding: 6
rightPadding: 6
bottomPadding: 6
leftPadding: 6
}
// A simple data model is loaded with just a header.
// This will be replaced when we load the real one
// from C++.
dataModel: XmlDataModel {
source: "model.xml"
}
listItemComponents: [
// The header list item displays a title along with a counter
// that displays the number of children
ListItemComponent {
type: "header"
HeaderListItem {
topMargin: 8
title: ListItemData.title
subtitle: (ListItem.initialized ?
ListItem.view.dataModel
.childCount(ListItem.indexPath) : 0);
}
},
// The contact list item displays the name of the contact
ListItemComponent {
type: "contacts"
StandardListItem {
title: ListItemData.title
}
}
]
}
// The activity indicator has an object name set so that
// we can start and stop it from C++
ActivityIndicator {
objectName: "indicator"
layoutProperties: DockLayoutProperties {
verticalAlignment: VerticalAlignment.Fill
horizontalAlignment: HorizontalAlignment.Fill
}
}
} // Ends the root Container
} // Ends the Page
} // Ends the TabbedPane
And this is my QML file:
import bb.cascades 1.0
TabbedPane {
activePane: Page {
actions: [
// An action item that calls the C++ function that retrieves
// the contact list
ActionItem {
title: "Refresh"
onTriggered: app.initiateRequest(txtGen.text)
}
]
content: Container {
layout: StackLayout {}
Button {
text: "Get Games"
onClicked: app.initiateRequest(txtGen.text)
}
Label {
text: "Enter Generation (1-5)"
}
TextField {
id: txtGen
}
// A list that has two list item components, one for a header
// and one for contact names. The list has an object name so
// that we can set the data model from C++.
ListView {
objectName: "list"
layout: FlowListLayout {
topPadding: 6
rightPadding: 6
bottomPadding: 6
leftPadding: 6
}
// A simple data model is loaded with just a header.
// This will be replaced when we load the real one
// from C++.
dataModel: XmlDataModel {
source: "model.xml"
}
listItemComponents: [
// The header list item displays a title along with a counter
// that displays the number of children
ListItemComponent {
type: "games"
HeaderListItem {
topMargin: 8
title: ListItemData.generation
subtitle: (ListItem.initialized ?
ListItem.view.dataModel
.childCount(ListItem.indexPath) : 0);
}
},
// The contact list item displays the name of the contact
ListItemComponent {
type: "game"
StandardListItem {
title: ListItemData.title
}
}
]
}
// The activity indicator has an object name set so that
// we can start and stop it from C++
ActivityIndicator {
objectName: "indicator"
layoutProperties: DockLayoutProperties {
verticalAlignment: VerticalAlignment.Fill
horizontalAlignment: HorizontalAlignment.Fill
}
}
} // Ends the root Container
} // Ends the Page
} // Ends the TabbedPane
I also have an xml folder stored under assets/model.xml of my projects directory, with the following contents:
<?xml version="1.0" encoding="utf-8"?>
<xml>
<games>
</games>
</xml>
Also, here is the App.cpp code I wrote:
#include "app.hpp"
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/Button>
#include <bb/cascades/TextField>
#include <QDir>
using namespace bb::cascades;
App::App()
{
// Load the QML document and retrieve the root node
QmlDocument *qml = QmlDocument::create("main.qml");
mRoot = qml->createRootNode<AbstractPane>();
// Retrieve the activity indicator from QML so that we can start
// and stop it from C++
mActivityIndicator = mRoot->findChild<ActivityIndicator*>("indicator");
// Retrieve the list so we can set the data model on it once
// we retrieve it
mListView = mRoot->findChild<ListView*>("list");
//mTextField = mRoot->findChild<TextField*>("textField");
//qDebug() << "Generation: " << mTextField->text();
// Expose this class to QML so that we can call its functions from there
qml->setContextProperty("app", this);
// Create a network access manager and connect a custom slot to its
// finished signal
mNetworkAccessManager = new QNetworkAccessManager(this);
Q_ASSERT(connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*))));
// Displays a warning message if there's an issue connecting the signal
// and slot. This is a good practice with signals and slots as it can
// be easier to mistype a slot or signal definition
//Q_ASSERT(result);
//Q_UNUSED(result);
// Create a file in the application's data directory
mFile = new QFile("data/model.xml");
// Set the scene using the root node
Application::setScene(mRoot);
}
void App::initiateRequest(QString text)
{
// Start the activity indicator
mActivityIndicator->start();
// Create and send the network request
QNetworkRequest request = QNetworkRequest();
request.setUrl(QUrl("http://192.168.1.109/TESTWEBSERVICE/MAKEXML.php")); //https://developer.blackberry.com/cascades/files/documentation/device_platform/networking/model.xml"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QByteArray bytes;
QUrl params;
params.addQueryItem(QString::fromStdString("generation"), text);
bytes = params.encodedQuery();
mNetworkAccessManager->post(request, bytes);
}
void App::requestFinished(QNetworkReply* reply)
{
// Check the network reply for errors
if (reply->error() == QNetworkReply::NoError) {
// Open the file and print an error if the file cannot be opened
if (!mFile->open(QIODevice::ReadWrite))
{
qDebug() << "\n Failed to open file";
return;
}
mFile->resize(0);
// Write to the file using the reply data and close the file
QByteArray xml = reply->readAll();
mFile->write(xml);
qDebug() << xml;
mFile->flush();
mFile->close();
// Create the data model using the contents of the file. The
// location of the file is relative to the assets directory.
XmlDataModel *dataModel = new XmlDataModel();
dataModel->setSource(QUrl("../../../data/model.xml"));
// Set the new data model on the list and stop the activity indicator
mListView->setDataModel(dataModel);
mActivityIndicator->stop();
}
else
{
qDebug() << "\n Problem with the network";
qDebug() << "\n" << reply->errorString();
}
}
And here is my App.h file:
#ifndef APP_H
#define APP_H
#include <QObject>
#include <QFile>
#include <bb/cascades/ActivityIndicator>
#include <bb/cascades/ListView>
#include <bb/cascades/XMLDataModel>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/TextField>
using namespace bb::cascades;
/*!
* @brief Application GUI object
*/
class App : public QObject
{
Q_OBJECT
public:
/*!
* Constructor.
*/
App();
/*!
* Initiates the network request.
*/
Q_INVOKABLE void initiateRequest(QString text);
private slots:
/*!
* Handles the network reply.
*/
void requestFinished(QNetworkReply* reply);
private:
AbstractPane *mRoot;
ActivityIndicator *mActivityIndicator;
ListView *mListView;
TextField *mTextField;
QNetworkAccessManager *mNetworkAccessManager;
QFile *mFile;
QString apiKey;
QString apiString;
};
#endif // ifndef APP_H
And the QDebug() stream prints out the following for the generation paramater being 1:
"<?xml version="1.0" encoding="utf-8"?>
<xml>
<games generation="1">
<game title="green">green</game>
<game title="red">red</game>
<game title="blue">blue</game>
<game title="yellow">yellow</game>
</games>
</xml>"
For generation paramater being 2:
"<?xml version="1.0" encoding="utf-8"?>
<xml>
<games generation="2">
<game title="gold">gold</game>
<game title="silver">silver</game>
<game title="crystal">crystal</game>
</games>
</xml>"
For generation paramater being 3:
"<?xml version="1.0" encoding="utf-8"?>
<xml>
<games generation="3">
<game title="ruby">ruby</game>
<game title="sapphire">sapphire</game>
<game title="emerald">emerald</game>
</games>
</xml>"
For Generation paramater being 4:
"<?xml version="1.0" encoding="utf-8"?>
<xml>
<games generation="4">
<game title="perl">perl</game>
<game title="diamond">diamond</game>
<game title="platinum">platinum</game>
</games>
</xml>"
For Generation paramater being 5:
"<?xml version="1.0" encoding="utf-8"?>
<xml>
<games generation="5">
<game title="black">black</game>
<game title="white">white</game>
<game title="black 2">black 2</game>
<game title="white 2">white 2</game>
</games>
</xml>"
For anything else, where paramater generation is foo (foo being a placeholder):
"<?xml version="1.0" encoding="utf-8"?>
<xml>
<games generation="foo">
</games>
</xml>"
I'm not quite sure, why this isn't working. I tried searching around for other examples, to see if I could use it to figure out how to make my code work properly, but that didn't work out.
I know this question is long, but I wanted to provide as much detail about what I was doing, in order to get the best answers. That being said, I don't really think the my main.cpp file is relevant, so I won't put it here, unless someone asks to see it.
(I added the title attribute to my game tags in my xml code and put the same values as the main data values in those tags, such as turning
<game>yellow</game>
into
<game title="yellow">yellow</game>
after trying to get it to work with out the title attribute just the former because I thought that might solve my problem, even though it was a bit hackish, but it didn't. Ideally, I want to figure out how to get this to work both ways, with and without the title attribute in the game tags.