Reporte IV BlackBerry 10 Web service
Este reporte corresponde al proyecto número 4 de la asignatura Desarrollo de Aplicaciones para Tecnologías Móviles.
NOTA El Web service que vamos a utilizar no es el mismo que el del proyecto anterior (cliente Windows Phone), se han hecho algunas modificaciones al modelo de datos y por lo tanto al Web service, para cumplir con las observaciones hechas por el profesor:
- El cliente no debe de modificar los catálogos (clientes y productos), únicamente agregar registros.
- La aplicación cliente no debe tener una réplica completa de la base de datos, únicamente los datos de los registros que se han realizado con la misma.
Definición del problema
Desarrollar un Web service (en el lenguaje que más les agrade) capaz de manipular una base de datos a petición del cliente, en este caso, el cliente será una aplicación móvil para la plataforma Blackberry 10.
Definiciones
Como este reporte forma parte de una serie de reportes, vamos a omitir la teoría sobre Web services puesto que ya se abordo en el reporte número II.
Propuesta de solución
Nuevamente, el problema nos da la flexibilidad de elegir que problema modelar, la propuesta es la siguiente:
Desarrollar una aplicación móvil para el control requisiciones, similar a los dispositivos que utilizan los agentes de venta cuando salen a levantar pedidos.
El cliente
La aplicación móvil deberá cumplir con los siguientes requisitos:
- La aplicación permitirá consultar la lista de clientes.
- La aplicación permitirá consultar la lista de productos.
- La aplicación permitirá registrar pedidos.
- La aplicación permitirá consultar la lista de pedidos.
- La aplicación deberá tener la capacidad de trabajar tanto online como offline.
- La aplicación permitirá subir los registros al servidor.
- La aplicación permitirá contactar al servidor para actualizar catálogos.
El servidor
Un Web service desarrollado en PHP que manipula una base de datos MySQL. A continuación la lista de servicios que vamos desarrollar:
- PutRegisters(requests[], details[]) Recibe los datos de la aplicación móvil y los agrega a la base de datos, en caso de que aun no existan.
- GetAllClients() Retorna al cliente la lista de clientes. Este servicio forma parte de la actualización de catálogos.
- GetAllProducts() Retorna al cliente la lista de productos. Este servicio forma parte de la actualización de catálogos.
Requisitos
Para poder realizar esta práctica se necesita lo siguiente:
- Debian GNU/Linux Wheezy
- Un servidor LAMP (Linux + Apache + MySQL + PHP), ya vimos como configurar uno del reporte número 2.
- Momentics IDE 2.0, fue mi primera opción pero después cambie a Qt Creator, solo lo incluyo porque al instalar éste IDE se descargan todas las bibliotecas necesarias.
- Qt 5 y Qt Creator, versiones beta (http://qt-project.org/downloads), solo descarga e instala con el clásico “Next, Next, Next…”.
- El simulador de Blackberry (developer.blackberry.com/devzone/develop/simulator/simulator_installing.html).
Instalación del simulador
El simulador es una imagen de disco que podemos usar con VMware Player (www.vmware.com), simplemente instala VMware Player y carga la imagen del simulador.
El simulador consume muchos recursos, por defecto el simulador buscará una tarjeta de gráficos en nuestro sistema, si no contamos con una nos encontraremos con esto:
La solución es entrar en uno de los modos seguros, en mi caso he optado por la resolución de pantalla 720x720 y modo seguro (opción 5).
Configuración de Qt Creator
Qt Creator ya viene con soporte para BlackBerry, sin embargo habrá que configurar algunas cosas. Vamos a asumir que Momentics IDE fue instalado en el directorio /opt/bbndk y Qt fue instalado en /opt/qt5.
Vamos a ejecutar Qt Creator desde la consola, pero antes vamos a ejecutar un script de configuración que viene incluido con Momentics IDE.
/opt/bbndk/bbndk-env_10_2_0_1155.sh
/opt/qt5/Tools/QtCreator/bin/qtcreator
El script de configuración exporta algunas variables de entorno que Qt Creator necesita para ubicar las herramientas necesarias. Una vez abierto Qt Creator, vamos a agregar el compilador: Tools -> Options -> Build & Run.
Ahora hay que configurar el simulador: Tool -> Options -> Devices -> Add…:
Especificamos la IP del simulador:
La IP la podemos encontrar en la parte inferior derecha del simulador:
Ahora deberíamos poder probar una aplicación “¡Hola, mundo!”:
Para más información sobre la configuración del entorno véase:
- http://qt-project.org/wiki/Setting-up-Qt-Creator-for-BlackBerry-and-QNX
- https://developer.blackberry.com/native/documentation/core/qt_porting_tools.html
Desarrollo del servidor
En el reporte anterior ya vimos como crear y registrar servicios en PHP usando NuSOAP, por ello vamos a mostrar únicamente lo esencial.
El modelo de datos
Nuestra base de datos consiste de 4 tablas, Cliente, Producto, Pedido y Detalle.
NOTA: Con el código fuente incluyo el modelo de la base de datos, fue diseñado utilizando MySQL Workbench y por lo tanto podrán exportar la base de datos desde ahí.
Los servicios
El web service no difiere mucho del que utilizamos en el proyecto anterior, se ha agregado el servicio GetAllProducts y el servicio Synchronize ha sido reemplazado con PutRegisters.
function GetAllProducts()
{$mysqli = OpenDB();
$mysqli->query("SET NAMES UTF8");
$query = "SELECT * FROM Producto";
$result = $mysqli->query($query);
$mysqli->close();
if ($result->num_rows == 0) {
return array();
}
$products = array();
while ($row = $result->fetch_assoc()) {
$products[] = array(
'IdProduct' => $row['IdProducto'],
'Description' => $row['Descripcion']
);
}
return $products;
}
function PutRegisters($RequestItems, $DetailItems)
{if (is_array($RequestItems)) {
$requests = to_request_array($RequestItems);
else {
} $requests = array();
}
if (is_array($DetailItems)) {
$details = to_detail_array($DetailItems);
else {
} $details = array();
}
$mysqli = OpenDB();
foreach ($requests as $r) {
$idRequest = $r['IdRequest'];
$requestIdClient = $r['IdClient'];
$requestDate = $r['RequestDate'];
if (findRequest($requestIdClient, $requestDate, $mysqli) == NOT_FOUND) {
$newIdRequest = CreateRequest($requestIdClient, $requestDate,
$mysqli);
foreach ($details as $d) {
$product = $d['IdProduct'];
$amount = $d['Amount'];
$detailIdRequest = $d['IdRequest'];
if ($idRequest != $detailIdRequest) continue;
($newIdRequest, $product, $amount, $mysqli);
CreateDetail
}
}
}
$mysqli->close();
return count($requests) + count($details);
}
La actualización de los datos con el servidor se ha simplificado puesto que ahora no tenemos que crear nuevos clientes.
La base de datos local
Puesto que nuestra aplicación cliente deberá funcionar también en modo offline, debe existir una réplica del modelo de datos en el cliente. En esta ocasión utilizaremos SQLite. Las entidades son las siguientes:
- Client Representa a la tabla Cliente.
- Request Representa a la tabla Pedido.
- Detail Representa a la tabla Detalle.
- Product Representa a la tabla Detalle.
-
Key Tabla auxiliar para almacenar las claves primarias de las tablas restantes.Debido a las modificaciones ya no es necesaria.
Inicialización de la base de datos
Cuando la aplicación inicia se debe preparar la base de datos, en caso de no existir ésta es creada. Dentro de la clase Backend podrán encontrar el siguiente código:
bool Backend::initDataBase()
{//removeDatabase(); return true;
QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE");
"foreign_key_constraints=ON");
database.setConnectOptions(
database.setDatabaseName(DB_PATH);
if (!database.open()) {
qDebug() << "ERROR: Could NOT open the database :(" << endl;
return false;
}
this->sda = new SqlDataAccess(DB_PATH);
"PRAGMA foreign_keys = ON"); // FORCE foreign key constraints
sda->execute(
if (sda->hasError())
qDebug() << "ERROR: " << sda->error() << endl;
sda->execute(CREATE_TABLE_CLIENT);if (sda->hasError()) {
qDebug() << "ERROR: " << sda->error() << endl;
return false;
}
sda->execute(CREATE_TABLE_REQUEST);
sda->execute(REQUEST_UNIQUE_CONSTRAINT);
if (sda->hasError()) {
qDebug() << "ERROR: " << sda->error() << endl;
return false;
}
sda->execute(CREATE_TABLE_PRODUCT);if (sda->hasError()) {
qDebug() << "ERROR: " << sda->error() << endl;
return false;
}
sda->execute(CREATE_TABLE_DETAIL);
sda->execute(DETAIL_UNIQUE_CONSTRAINT);if (sda->hasError()) {
qDebug() << "ERROR: " << sda->error() << endl;
return false;
}
return true;
}
Operaciones con la base de datos
Para simplificar la lógica de la aplicación se crearon varias funciones auxiliares para las diferentes operaciones, como son insertar registros, consultas, etc.
Inserción de registros:
bool Backend::insertClient(int id, QString name, QString middleName,
QString lastName, QString address)
{
QVariantList client;
client << id << name << middleName << lastName << address;QString values = QString("VALUES(%1, '%2', '%3', '%4', '%5')")
.arg(to_s(id), name, middleName, lastName, address);
QString query = "INSERT INTO Client(IdClient, Name, MiddleName, "
" LastName, Address) " + values;
sda->execute(query);if (sda->hasError()) {
qDebug() << "ERROR: " << sda->error() << endl;
return false;
}
return true;
}
bool Backend::insertProduct(int id, QString description)
{QString values = QString("VALUES(%1, '%2')").arg(to_s(id), description);
"INSERT INTO PRODUCT(IdProduct, Description) " + values);
sda->execute(
if (sda->hasError()) {
qDebug() << "ERROR::: " << sda->error() << endl;
return false;
}
return true;
}
int Backend::insertRequest(int idClient, QString date)
{QString values = QString("VALUES(%1, '%2')").arg(to_s(idClient), date);
QString query = "INSERT INTO Request(IdClient, RequestDate) " + values;
QSqlQuery Q = sda->connection().exec(query);
//qDebug() << "LAST INSERTED ID: " << Q.lastInsertId().toInt() << endl;
if (sda->hasError()) {
qDebug() << "ERROR: " << sda->error() << endl;
return -1;
}
return Q.lastInsertId().toInt();
}
bool Backend::insertDetail(int idRequest, int idProduct, int amount)
{QString idr = to_s(idRequest);
QString a = to_s(amount);
QString p = to_s(idProduct);
QString values = QString("VALUES(%1, %2, %3)").arg(idr, p, a);
"INSERT INTO Detail(IdRequest, IdProduct, Amount) " + values);
sda->execute(
if (sda->hasError()) {
qDebug() << "ERROR: " << sda->error() << endl;
return false;
}
return true;
}
Consulta de registros:
QVector<Client> Backend::selectClients()
{QString query = "SELECT * FROM Client";
QVariant result = sda->execute(query);
QVariantList list = result.value<QVariantList>();int n = list.size();
QVector<Client> clients;
for (int i = 0; i < n; i++) {
Client c;
QVariantMap map = list.at(i).value<QVariantMap>();"IdClient"].toString().toInt());
c.setIdClient(map["Name"].toString());
c.setName(map["MiddleName"].toString());
c.setMiddleName(map["LastName"].toString());
c.setLastName(map["Address"].toString());
c.setAddress(map[
clients.append(c);
}return clients;
}
QVector<Product> Backend::selectProducts()
{QString query = "SELECT * FROM Product";
QVariant result = sda->execute(query);
QVariantList list = result.value<QVariantList>();int n = list.size();
QVector<Product> products;
for (int i = 0; i < n; i++) {
Product p;
QVariantMap map = list.at(i).value<QVariantMap>();"IdProduct"].toString().toInt());
p.setIdProduct(map["Description"].toString());
p.setDescription(map[
products.append(p);
}
return products;
}
QVector<Request> Backend::selectRequests()
{QString query = "SELECT * FROM Request";
QVariant result = sda->execute(query);
QVariantList list = result.value<QVariantList>();int n = list.size();
QVector<Request> requests;
for (int i = 0; i < n; i++) {
Request r;
QVariantMap map = list.at(i).value<QVariantMap>();"IdRequest"].toString().toInt());
r.setIdRequest(map["IdClient"].toString().toInt());
r.setIdClient(map["RequestDate"].toString());
r.setRequestDate(map[
requests.append(r);
}
return requests;
}
QVector<Detail> Backend::selectDetails()
{QString query = "SELECT * FROM Detail";
QVariant result = sda->execute(query);
QVariantList list = result.value<QVariantList>();int n = list.size();
QVector<Detail> details;
for (int i = 0; i < n; i++) {
QVariantMap map = list.at(i).value<QVariantMap>();
Detail d;"IdDetail"].toString().toInt());
d.setIdDetail(map["IdRequest"].toString().toInt());
d.setIdRequest(map["IdProduct"].toString().toInt());
d.setIdProduct(map["Amount"].toString().toInt());
d.setAmount(map[
details.append(d);
}
return details;
}
Creo que no hay necesidad de explicar el código ya que se explica a sí mismo.
Comunicación con el Web service
Qt no proporciona mucho soporte para trabajar con Web services (¿Me equivoco?) pero si lo necesario para comunicarnos con el servidor, para extraer los datos de los mensajes que nos regrese el servidor habrá que ensuciarnos las manos un poquito.
Vamos a ilustrar la comunicación con uno de los servicios, GetAllClients, el resto de las operaciones son similares. Primero creamos un método llamado queryClients(), el cual envía la solicitud al servidor con el mensaje adecuado:
void Backend::queryClients()
{QUrl url(WS_URL);
QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
QNetworkRequest request(url);
QNetworkRequest::ContentTypeHeader, "text/xml");
request.setHeader(QObject::connect(networkManager, SIGNAL(finished(QNetworkReply *)),
this, SLOT(getClientsFinished(QNetworkReply*)));
networkManager->post(request, MSG_GET_ALL_CLIENTS.toAscii()); }
El valor de WS_URL
en nuestro caso es http://192.168.1.200/request_ws/index.php
, la constante MSG_GET_ALL_CLIENTS
almacena el mensaje SOAP que solicita la lista de clientes:
<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:req="Request">
<soapenv:Header/>
<soapenv:Body>
<req:GetAllClients/>
</soapenv:Body>
</soapenv:Envelope>
La comunicación con el servidor se realiza de manera asíncrona, el método que recibirá la respuesta del servidor es getClientsFinished():
void Backend::getClientsFinished(QNetworkReply *reply)
{QByteArray ans = reply->readAll();
QString response(ans.data());
QVector<Client> clients = toClientArray(response);
int n = clients.size();
for (int i = 0; i < n; i++) {
insertClient(clients[i].idClient(),
clients[i].name(),
clients[i].middleName(),
clients[i].lastName(),
clients[i].address());
}
loadClientsToList(); }
El contenido de la variable response almacena la respuesta del servidor en formato XML, por lo que hay que procesarla para obtener los datos que nos interesan, esto se realiza en el método toClientArray(), que devuelve una lista de clientes:
QVector<Client> Backend::toClientArray(QString message)
{QVector<Client> list;
Client client;QXmlStreamReader reader(message);
while (!reader.atEnd()) {
reader.readNext();QString name = reader.name().toString();
if (name == "IdClient" || name == "Name" || name == "MiddleName" ||
"LastName" || name == "Address") {
name == QString value = reader.readElementText();
if (name == "IdClient") {
client.setIdClient(value.toInt());else if (name == "Name") {
}
client.setName(value);else if (name == "MiddleName") {
}
client.setMiddleName(value);else if (name == "LastName") {
}
client.setLastName(value);else if (name == "Address") {
}
client.setAddress(value);
list.append(client);
}
}
}
return list;
}
En el código anterior nos apoyamos de la clase QXmlStreamReader para extraer los valores.
El cliente
Ahora veamos como crear una aplicación para BlackBerry que consuma nuestros servicios. El código es un poco extenso así que se explicarán únicamente las partes más importantes.
Hasta hace poco la tecnología empleada para crear aplicaciones BlackBerry era Java, sin embargo, en un intento por permanecer en el mercado, Blackberry ha apostado también por los dispositivos touch y ha reemplazado Java por C++, la tecnología se conoce como Cascades, un framework basado en Qt y QML para diseñar interfaces gráficas de manera nativa.
La página principal
Así lucirá la página principal de nuestra aplicación:
El diseño de esta página en QML es el siguiente:
Tab {title: "Inicio"
imageSource: "asset:///pictures/home.png"
NavigationPane {id: homeNavigationPane
objectName: "homeNavigationPane"
Page {id: home
Container {layout: DockLayout { }
Container {verticalAlignment: VerticalAlignment.Top
layout: StackLayout {
orientation: LayoutOrientation.LeftToRight
}
ImageView {imageSource: "asset:///pictures/LogoITCH.png"
}
Label {multiline: true
leftMargin: 50
textStyle {fontSize: FontSize.Large
}text: "Desarrollo de Aplicaciones\npara Tecnologías Móviles"
}
}
Container {verticalAlignment: VerticalAlignment.Center
horizontalAlignment: HorizontalAlignment.Fill
layout: StackLayout { }
Button {text: "Enviar registros al servidor"
horizontalAlignment: HorizontalAlignment.Fill
onClicked: {
.push(progressPage)
homeNavigationPane.putRegisters()
backend
}
}
Button {text: "Limpiar registros y actualizar catálogos"
horizontalAlignment: HorizontalAlignment.Fill
onClicked: {
.push(progressPage)
homeNavigationPane.updateCatalogs()
backend
}
}
}
}
}
} }
Registro de pedidos
La siguiente imagen muestra la interfaz de usuario para registrar nuevos pedidos.
El diseño de la interfaz en QML es el siguiente:
Tab {title: "Pedidos"
imageSource: "asset:///pictures/shop.png"
NavigationPane {id: requestNavigationPane
objectName: "requestNavigationPane"
Page {
Container {layout: StackLayout { }
DropDown {horizontalAlignment: HorizontalAlignment.Fill
title: "Cliente"
objectName: "clientList"
id: clientList
enabled: true
}
Container {layoutProperties: StackLayoutProperties {
spaceQuota: 4
}
ListView {objectName: "newItems"
listItemComponents: ListItemComponent {
type: "item"
Container {
Container {layout: StackLayout {
orientation: LayoutOrientation.LeftToRight
}
Label {layoutProperties: StackLayoutProperties {
spaceQuota: 1
}
text: ListItemData.amount
}
Label {layoutProperties: StackLayoutProperties {
spaceQuota: 6
}
text: ListItemData.description
}
}
Divider {horizontalAlignment: HorizontalAlignment.Fill
}
}
}
}
}
Container {layoutProperties: StackLayoutProperties {
spaceQuota: 1
}layout: StackLayout {
orientation: LayoutOrientation.LeftToRight
}
DropDown {objectName: "productList"
id: productList
layoutProperties: StackLayoutProperties {
spaceQuota: 6
}title: "Producto"
enabled: true
}
TextField {id: itemAmount
layoutProperties: StackLayoutProperties {
spaceQuota: 2
}hintText: "Cantidad"
}
Button {attachedObjects: [
SystemToast {id: productToast
body: "Seleciona un producto."
}
]
imageSource:"asset:///pictures/add.png"
layoutProperties: StackLayoutProperties {
spaceQuota: 1
}onClicked: {
var selectedOption = productList.selectedOption
if (selectedOption !== null) {
var idProduct = selectedOption.value
var description = selectedOption.text
console.log("IdProduct = " + idProduct)
var ok = backend.addDetail(idProduct, description, itemAmount.text)
if (ok) {
.text = ""
itemAmount.resetSelectedOption()
productList
}else {
} .show()
productToast
}
}
}
}
Button {attachedObjects: [
SystemToast {id: clientToast
body: "No hay nada que guardar."
}
]
horizontalAlignment: HorizontalAlignment.Fill
text: "Guardar pedido"
onClicked: {
var selectedOption = clientList.selectedOption
if (selectedOption !== null) {
var idClient = selectedOption.value
console.log("IdClient = " + idClient)
var ok = backend.saveRequest(idClient)
if (ok) {
.text = ""
idClient.resetSelectedOption()
clientList
}else {
} .show()
clientToast
}
}
}
}
}
} }
Cuando el usuario presiona el botón para guardar el pedido, se invoca al método saveReqeust() de la clase Backend con el ID del cliente para que almacene el pedido en la base de datos, la lista de artículos artículos se almacena en la clase Backend, por lo que no hay necesidad de enviársela. Aquí el código:
bool Backend::saveRequest(QString idClient)
{int idc = idClient.toInt();
if (_newDetails->isEmpty()) {
"Debe haber al menos un artículo.");
toast->setBody(
toast->show();return false;
}
QDate date = QDate::currentDate();
QTime time = QTime::currentTime();
QString year = to_s(date.year());
QString month = to_s(date.month());
QString day = to_s(date.day());
QString hour = to_s(time.hour());
QString minute = to_s(time.minute());
QString second = to_s(time.second());
QString rightNow = year + "-" + month + "-" + day + " "
":" + minute + ":" + second;
+ hour +
Key keys = getKeys();int idr = keys.newIdRequest();
if (!insertRequest(idr, idc, rightNow))
return false;
int n = _newDetails->size();
for (int i = 0; i < n; i++) {
keys = getKeys();int idd = keys.newIdDetail();
int idProduct = _newDetails->at(i).idProduct();
int amount = _newDetails->at(i).amount();
if (!insertDetail(idd, idr, idProduct, amount))
return false;
}
"¡Exito!");
toast->setBody(
toast->show();
_newDetails->clear();
_newDetailsModel->clear();return true;
}
Consulta de pedidos
Nuestra aplicación también permite consultar la lista de pedidos, aquí la interfaz:
El diseño en QML es el siguiente:
Tab {imageSource: "asset:///pictures/search.png"
Page {id: queryPage
paneProperties: NavigationPaneProperties {
backButton: ActionItem {
onTriggered: {
.pop()
requestNavigationPane
}
}
}
Container {
Container {layout: StackLayout {
orientation: LayoutOrientation.LeftToRight
}
Label {layoutProperties: StackLayoutProperties {
spaceQuota: 5
}
text: "Lista de pedidos"
}
Button {layoutProperties: StackLayoutProperties {
spaceQuota: 1
}imageSource: "asset:///pictures/refresh.png"
onClicked: {
.loadRequestsToList()
backend
}
}
}
ListView {objectName: "listOfRequests"
listItemComponents: ListItemComponent {
type: "item"
Container {layout: StackLayout { }
Container {layout: StackLayout { orientation: LayoutOrientation.LeftToRight }
Label {layoutProperties: StackLayoutProperties { spaceQuota: 1 }
}
Label {layoutProperties: StackLayoutProperties { spaceQuota: 1 }
text: ListItemData.amount
}
Label {layoutProperties: StackLayoutProperties { spaceQuota: 6 }
text: ListItemData.description
}
}
Divider {horizontalAlignment: HorizontalAlignment.Fill
}
}
}
}
}
} }
La lógica detrás de esta operación se encuentra en el método loadRequestsToList():
void Backend::loadRequestsToList()
{QString query = "SELECT Request.IdRequest, Client.Name, Client.MiddleName, "
"Client.LastName, Product.Description, Detail.Amount, "
"Request.RequestDate FROM Request, Client, Detail, Product "
"WHERE Detail.IdRequest = Request.IdRequest AND "
"Request.IdClient = Client.IdClient AND "
"Detail.IdProduct = Product.IdProduct";
QVariant result = sda->execute(query);
if (sda->hasError()) {
qDebug() << "ERROR: " << sda->error() << endl;
}
QVariantList list = result.value<QVariantList>();
int n = list.size();
_requestModel->clear();
QVariantMap entry;
QVariantMap row;for (int i = 0; i < n; i++) {
row = list.at(i).value<QVariantMap>();QString id = row["IdRequest"].toString();
QString client = row["Name"].toString() + " "
"MiddleName"].toString() + " "
+ row["LastName"].toString();
+ row[QString date = row["RequestDate"].toString();
"Request"] = id + ": " + client + " (" + date + ")";
entry[
"description"] = row["Description"].toString();
entry["amount"] = row["Amount"].toString();
entry[
_requestModel->insert(entry);
} }
El objeto *_requestModel* esta enlazado a un objeto ListView en la interfaz gráfica, por lo que los cambios que le ocurren al objeto *_requestModel* se reflejan automáticamente en el ListView.
Subir los registros al servidor
Para esta operación solo tenemos que presionar un botón y se nos mostrará una barra de progreso que simulará el progreso de la operación.
El código que realiza esta operación es el siguiente:
void Backend::putRegisters()
{QUrl url(WS_URL);
QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
QNetworkRequest request(url);
QNetworkRequest::ContentTypeHeader, "text/xml");
request.setHeader(QObject::connect(networkManager, SIGNAL(finished(QNetworkReply *)),
this, SLOT(putRequestsFinished(QNetworkReply*)));
networkManager->post(request, formattedRegisters().toAscii());"Enviando registros...");
_progressMessage->setText(50);
_progressIndicator->setValue( }
El método formattedRegisters() consulta los registros necesario y los estructura en un formato SOAP para que el servidor pueda interpretar los datos que le enviamos:
QString Backend::formattedRegisters()
{QString header = "<soapenv:Envelope "
"xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'"
" xmlns:req='Request'> <soapenv:Header/> <soapenv:Body> "
" <req:PutRegisters>";
QString footer = "</req:PutRegisters> </soapenv:Body> </soapenv:Envelope>";
QString requests = "\n";
"<RequestItems>";
requests += "\n";
requests +=
QVector<Request> R = selectRequests();
int n = R.size();
for (int i = 0; i < n; i++) {
"<item>";
requests += "<IdRequest>" + to_s(R[i].idRequest()) + "</IdRequest>";
requests += "<IdClient>" + to_s(R[i].idClient())+ "</IdClient>";
requests += "<RequestDate>" + R[i].requestDate() + "</RequestDate>";
requests += "</item>";
requests += "\n";
requests +=
}"</RequestItems>";
requests +=
QString details = "<DetailItems>";
"\n";
details += QVector<Detail> D = selectDetails();
n = D.size();for (int i = 0; i < n; i++) {
"<item>";
details += "<IdDetail>" + to_s(D[i].idDetail()) + "</IdDetail>";
details += "<IdRequest>" + to_s(D[i].idRequest()) + "</IdRequest>";
details += "<IdProduct>" + to_s(D[i].idProduct()) + "</IdProduct>";
details += "<Amount>" + to_s(D[i].amount()) + "</Amount>";
details += "</item>";
details += "\n";
details +=
}"</DetailItems>";
details +=
QString message = header + requests + "\n" + details + "\n" + footer;
return message;
}
La actualización de catálogos es muy similar.
Como lo indica el texto del botón, esta operación borra los registros de la base de datos local y actualiza los catálogos con el servidor, he aquí el código:
void Backend::updateCatalogs()
{
removeDatabase();"Limpiando registros");
_progressMessage->setText(25);
_progressIndicator->setValue(
initDataBase();
"Descargando catalogo de clientes...");
_progressMessage->setText(50);
_progressIndicator->setValue(
queryClients();"Descargando catalogo de clientes...");
_progressMessage->setText(75);
_progressIndicator->setValue(
queryProducts(); }
La operación queryClients() ya la vimos anteriormente cuando explicamos la comunicación con el Web service, solo falta mostrar como se obtiene la lista de productos:
void Backend::queryProducts()
{url(WS_URL);
QUrl *networkManager = new QNetworkAccessManager(this);
QNetworkAccessManager request(url);
QNetworkRequest .setHeader(QNetworkRequest::ContentTypeHeader, "text/xml");
requestQObject::connect(networkManager, SIGNAL(finished(QNetworkReply *)),
this, SLOT(getProductsFinished(QNetworkReply*)));
->post(request, MSG_GET_ALL_PRODUCTS.toAscii());
networkManager
}
void Backend::getProductsFinished(QNetworkReply *reply)
{= reply->readAll();
QByteArray ans response(ans.data());
QString <Product> products = toProductArray(response);
QVector
= products.size();
int n for (int i = 0; i < n; i++) {
insertProduct(products[i].idProduct(), products[i].description());
}
loadProductsToList();
}
Y bueno, mi intención no es abrumarlos con mucho código, creo que las partes más importantes ya se han explicado. Al final se proporciona el código completo para que prueben la aplicación si así lo desean.
Código fuente
El código tanto del cliente como del servidor están disponibles en Bitbucket en las siguientes direcciones:
https://bitbucket.org/rendon/request_ws https://bitbucket.org/rendon/requisition_bb10
O bien pueden clonar los proyectos:
$ git clone https://rendon@bitbucket.org/rendon/request_ws.git
$ git clone https://rendon@bitbucket.org/rendon/requisition_bb10.git
La licencia del Web service y del la aplicación cliente es GPLv3.
Por hacer
Esta es la primara vez que utilizo Qt para algo más que un simple “Hola, mundo!”, y la primera vez que utilizo QML, seguramente existe una mejor manera de realizar las cosas, Qt emplea el patrón de diseño Model View, sin embargo, al ser la primera vez, me enfoque más en que las cosas funcionaran. Lo cierto es que apegarse a un patrón de diseño ayuda a dar mantenimiento a nuestro software de forma más fácil.