Come utilizzare il componente GeoLocation all’interno di un controllo MapView

Con il rilascio delle ultime versioni dell’SDK Cascades for BlackBerry 10 sono state implementate le parti mancanti delle API relative alla gestione dei DataProvider per le geolocalizzazioni di POI all’interno del controllo MapView. Fino ad oggi, se volevamo implementare nella nostra applicazione la visualizzazione di una mappa avevamo due scelte:

- utilizzare le API di Google, OpenStreetMap o Bing all’interno di una WebView
- utilizzare il componente MapView e costruire su di esso un layer (da mantenere allineato) sul quale visualizzare i POI.

Quest’ultimo ovviamente comportava un notevole lavoro sulla UI che come conseguenza portava a non avere la user experience che ci si aspetta da un’applicazione “nativa” sviluppata per BlackBerry 10.

Grazie al rilascio delle nuove API, abbiamo la possibilità di utilizzare pienamente le mappe BlackBerry e i relativi Provider, così da poter garantire all’utente la stessa esperienza d’uso delle mappe BlackBerry nella nostra applicazione.

Integrare il componente GeoLocation nella nostra applicazione è semplice e veloce. Supponiamo di avere un’applicazione che a seguito di una selezione dell’utente debba visualizzare una mappa con riportata la nostra posizione e quella della farmacia più vicina a noi.

Come prima cosa aggiungiamo nel file .PRO il riferimento alle seguenti librerie:

- lbbcascadesmaps
- lQtLocationSubset
- lGLESv1_CM

Queste librerie consentono di poter accedere al servizio di geolocalizzazione e alle mappe BlackBerry.

TEMPLATE = app
TARGET = app

CONFIG += qt warn_on debug_and_release cascades

INCLUDEPATH += ../src
SOURCES += ../src/*.cpp
HEADERS += ../src/*.hpp ../src/*.h
LIBS += -lbbsystem
LIBS += -lbb
LIBS += -lbbdevice
LIBS += -lbbdata -lbbcascadesmaps -lQtLocationSubset -lGLESv1_CM

lupdate_inclusion {
    SOURCES += ../assets/*.qml
    SOURCES += ../assets/component/*.qml
}
...
...

Aggiungiamo nel file “bar-descriptor.xml“, nella sezione Permission, l’autorizzazione a utilizzare il servizio di geolocalizzazione e l’accesso a internet

...
...
<!-- Request permission to execute native code.  Required for native applications. -->
<permission system="true">run_native</permission>
<permission>read_geolocation</permission>
<permission>access_internet</permission>
<permission>access_location_services</permission>
...
...

Adesso, aggiungiamo nella classe di avvio della nostra applicazione il codice che crea e associa i POI da geolocalizzare

...
...
#include <bb/cascades/maps/MapView>
#include <bb/cascades/maps/MapData>
#include <bb/cascades/maps/DataProvider>
#include <bb/platform/geo/Point>
#include <bb/platform/geo/GeoLocation>
#include <bb/platform/geo/Marker>
#include <bb/UIToolkitSupport>
...
...
using namespace bb::cascades::maps;
using namespace bb::platform::geo;

// start applicazione
HelpMe::HelpMe(bb::cascades::Application *app)
    : QObject(app)
    , m_appConfig(new AppConfig(this))
{
	...
	...
    // Creo l'oggetto root dell'applicazione
    AbstractPane *root = qml->createRootObject<AbstractPane>();
	// recupero l'oggetto MapView
	QObject* mapViewAsQObject = root->findChild<QObject*>(QString("mapViewPoiPharmacy"));
	// se l'oggetto viene trovato, crea l'oggetto che visualizza la posizione del device
	if (mapViewAsQObject) {
		// cast dell'oggetto al tipo corretto
		mapView = qobject_cast<bb::cascades::maps::MapView*>(mapViewAsQObject);
		// imposta la visibilità del tasto "Naviga To..."
		mapView->setCaptionGoButtonVisible(true);
		// se l'oggetto mapView è valido, crea il PIN che identifica 
		// la posizione del device e lo aggiunge ai dati della mappa
		if (mapView) {
			// crea l'archivio dei dati della mappa
			DataProvider* myDeviceLocDataProv = new DataProvider("my-device-location-data-provider");
			// lo aggiunge alla mappa
			mapView->mapData()->addProvider(myDeviceLocDataProv);
			// crea il bullet
			deviceLocation = new GeoLocation("current-position-id");
			deviceLocation->setName("Posizione corrente");
			deviceLocation->setDescription("Riporta la mia attuale posizione sulla mappa.");
			// Per identificare diversamente la nostra posizione
			// modifichiamo il simbolo del Marker (PIN del POI)
			Marker myPosMarker = Marker(UIToolkitSupport::absolutePathFromUrl(
									QUrl("asset:///images/my_position_mark.png")), QSize(60, 60),
									QPoint(29, 29), QPoint(29, 1));
			deviceLocation->setMarker(myPosMarker);
			myDeviceLocDataProv->add(deviceLocation);
		}
	}
    // attiva l'applicazione
	app->setScene(root);
}
...
...

Aggiungiamo inoltre il metodo che si occuperà di visualizzare i POI dopo che il dispositivo avrà determinato la posizione corretta.

...
...
/*
 * imposta la posizione corrente e quella del poi più vicino
 */
void HelpMe::updateDeviceLocation(double lat, double lon, double poiLat, double poiLon) {
	// imposta la mia posizione
	if (deviceLocation) {
		deviceLocation->setLatitude(lat);
		deviceLocation->setLongitude(lon);
	}
	mapView->setLatitude(lat);
	mapView->setLongitude(lon);
	// imposta la posizione del POI
	GeoLocation *pharmPos = new GeoLocation("pharmacy-position-id");
	pharmPos->setLatitude(poiLat);
	pharmPos->setLongitude(poiLon);
	pharmPos->setName("Posizione farmacia più vicina");
	pharmPos->setDescription("Farmacia di test, via della Prova 5 - Monza");
	mapView->mapData()->add(pharmPos);
}
...
...

A questo punto non resta che aggiungere nella nostra pagina QML che visualizza la mappa l’oggetto PositionSource per gestire il GPS.

...
...
attachedObjects: [
    PositionSource {
    	// componente Qt per gestire il GPS
        id: positionSource
        updateInterval: 1000
        // attiva/disattiva il GPS
        active: retrieveGpsPosition
        onPositionChanged: {
        	// quando la posizione viene individuata, le coordinate vengono lette, la mappa è
        	// resa visibile e il GPS viene spento. Quindi si richiama il C++ per 
        	// visualizzare sulla mappa la nostra posizione e il POI più vicino
            lat.text = positionSource.position.coordinate.latitude;
            lon.text = positionSource.position.coordinate.longitude;
            mapview.visible = true;
            gpsLoadIndicator.stop();
            retrieveGpsPosition = false;
            _helpme.updateDeviceLocation(positionSource.position.coordinate.latitude, positionSource.position.coordinate.longitude, latitudePOI, longitudePOI);
        }
    }
]
...
...

Vediamo adesso il codice completo.

Sorgente “FindMe.qml”

/**
* FindMe.qml
* 
* Summary: visualizza la mappa con la mia posizione e un la prima farmacia vicina
* 
* Author:  Nicola D'Amico
* 			Italian Developer for BlackBerry® smartphones
* 			Monza (MB) - Italy
* 
* Date: 15.05.2013
* 
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
* 
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
import bb.cascades 1.0
import QtMobility.sensors 1.2
import bb.cascades.maps 1.0
import QtMobilitySubset.location 1.1

Page {
	// ------------------------------------------------------------------
	// retrieveGpsPosition: indica se il GPS è attivo e deve essere 
	// 						trovata la posizione
	// latitudePOI: latitudine della farmacia
	// longitudePOI: longitudine della farmacia
	// ------------------------------------------------------------------
    property bool retrieveGpsPosition: false
    property double latitudePOI: 45.58169
    property double longitudePOI: 9.301695
    Container {
        id: root
        layout: DockLayout {
        }
        // activityIndicator visualizzato durante la ricerca della posizione
        ActivityIndicator {
            id: gpsLoadIndicator
            preferredWidth: 400
            preferredHeight: 400
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
        }
        // componente MapView utilizzato per visualizzare i POI
        MapView {
            id: mapview
            objectName: "mapViewPoiPharmacy"
            altitude: _appConfig.Altitude
            preferredWidth: _appConfig.DisplayWidth
            preferredHeight: _appConfig.DisplayHeight
            visible: false
        }
        // container che visualizza le coordinate della nostra posizione
        Container {
            horizontalAlignment: HorizontalAlignment.Fill
            verticalAlignment: VerticalAlignment.Top
            topPadding: 5
            leftPadding: 5
            bottomPadding: 5
            background: Color.create("#ddffffff")
            Container {
                layout: StackLayout {
                    orientation: LayoutOrientation.LeftToRight
                }
                horizontalAlignment: HorizontalAlignment.Center
                Label {
                	// latitudine
                    id: lat
                    textStyle {
                        base: SystemDefaults.TextStyles.SmallText
                        color: Color.Black
                        fontWeight: FontWeight.Bold
                    }
                }
                Label {
                	// longitudine
                    id: lon
                    textStyle {
                        base: SystemDefaults.TextStyles.SmallText
                        color: Color.Black
                        fontWeight: FontWeight.Bold
                    }
                }
            }
        }
    }
    attachedObjects: [
        PositionSource {
        	// componente Qt per gestire il GPS
            id: positionSource
            updateInterval: 1000
            // attiva/disattiva il GPS
            active: retrieveGpsPosition
            onPositionChanged: {
            	// quando la posizione viene individuata, le coordinate vengono lette, la mappa è
            	// resa visibile e il GPS viene spento. Quindi si richiama il C++ per 
            	// visualizzare sulla mappa la nostra posizione e il POI più vicino
                lat.text = positionSource.position.coordinate.latitude;
                lon.text = positionSource.position.coordinate.longitude;
                mapview.visible = true;
                gpsLoadIndicator.stop();
                retrieveGpsPosition = false;
                _helpme.updateDeviceLocation(positionSource.position.coordinate.latitude, positionSource.position.coordinate.longitude, latitudePOI, longitudePOI);
            }
        }
    ]
    actions: [
        ActionItem {
            title: qsTr("Chiudi")
            ActionBar.placement: ActionBarPlacement.OnBar
            imageSource: "asset:///images/ic_close.png"
            onTriggered: {
                findMeSheet.close();
            }
        },
        ActionItem {
            title: qsTr("Aiuto")
            imageSource: "asset:///images/ic_help.png"
            ActionBar.placement: ActionBarPlacement.InOverflow
            onTriggered: {
                helpView.open()
            }
        },
        ActionItem {
            title: qsTr("Aggiorna posizione GPS")
            imageSource: "asset:///images/gps_fix_81.png"
            ActionBar.placement: ActionBarPlacement.InOverflow
            onTriggered: {
            	// impostando a true "retrieveGpsPosition" automaticamente si 
            	// scatena l'evento che attiva il GPS
                retrieveGpsPosition = true;
                gpsLoadIndicator.start();
            }
        },
        InvokeActionItem {
            title: qsTr("Invia la posizione")
            ActionBar.placement: ActionBarPlacement.InOverflow
            query {
                mimeType: "text/plain"
                invokeActionId: "bb.action.SHARE"
            }
            onTriggered: {
            	// aggiungere il codice necessario per lo share
                ...
                ...
                data = "[messaggio da inviare]"
                ...
                ...
            }
        }
    ]
}

Sorgente HelpMe.hpp

/**
 * HelpMe.hpp
 *
 * Summary: header file della classe HelpMe.cpp
 *
 * Author:  Nicola D'Amico
 * 			Italian Developer for BlackBerry® smartphones
 * 			Monza (MB) - Italy
 *
 * Date: 15.05.2013
 *
 * Twitter: http://twitter.com/WhiteSharkIT
 * Linkedin: http://it.linkedin.com/in/nicoladamico
 *
 * Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
 */
#ifndef HelpMe_HPP
#define HelpMe_HPP

#include <QtCore/QObject>

class AppConfig;

namespace bb { namespace cascades {class Application; namespace maps {class MapView;}}namespace platform {namespace geo {class GeoLocation;}}}

/*
 * classe manager per l'accesso a tutte le informazioni dell'applicazione
 * e renderle disponibili nella UI
 */
class HelpMe : public QObject
{
    Q_OBJECT

    // classe AppConfig
    Q_PROPERTY(AppConfig* appConfig READ appConfig CONSTANT);

public:
    HelpMe(bb::cascades::Application *app);
    virtual ~HelpMe() {}

    Q_INVOKABLE void updateDeviceLocation(double lat, double lon);

private:
    // Classe che gestisce la configurazione dell'applicazione
    AppConfig* appConfig() const;
    AppConfig* m_appConfig;

	bb::cascades::maps::MapView* mapView;
	bb::platform::geo::GeoLocation* deviceLocation;

};

#endif

Sorgente HelpMe.cpp

/**
 * HelpMe.cpp
 *
 * Summary: classe HelpMe start dell'applicazione
 *
 * Author:  Nicola D'Amico
 * 			Italian Developer for BlackBerry® smartphones
 * 			Monza (MB) - Italy
 *
 * Date: 15.05.2013
 *
 * Twitter: http://twitter.com/WhiteSharkIT
 * Linkedin: http://it.linkedin.com/in/nicoladamico
 *
 * Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
 */
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/ActionItem>
#include <bb/cascades/Sheet>
#include <bb/cascades/NavigationPane>
#include <bb/cascades/VisualStyle>
#include <bb/cascades/ThemeSupport>
#include <bb/cascades/Theme>
#include <bb/cascades/ColorTheme>
#include <bb/ApplicationInfo>
#include <bb/data/DataSource>
#include <bb/data/SqlConnection>
#include <bb/cascades/maps/MapView>
#include <bb/cascades/maps/MapData>
#include <bb/cascades/maps/DataProvider>
#include <bb/platform/geo/Point>
#include <bb/platform/geo/GeoLocation>
#include <bb/platform/geo/Marker>
#include <bb/UIToolkitSupport>

#include <bb/system/SystemDialog>
#include <bb/system/SystemToast>

#include "HelpMe.hpp"
#include "AppConfig.hpp"
#include "SqlManager.hpp"

using namespace bb;
using namespace bb::cascades;
using namespace bb::cascades::maps;
using namespace bb::platform::geo;

// start applicazione
HelpMe::HelpMe(bb::cascades::Application *app)
    : QObject(app)
    , m_appConfig(new AppConfig(this))
{
	// registro il systemDialog per poterlo utilizzare nell'UI
	qmlRegisterType<bb::system::SystemDialog>("bb.system", 1, 0, "SystemDialog");
	qmlRegisterType<bb::system::SystemToast>("bb.system", 1, 0, "SystemToast");
	// registra le classi per essere utilizzate nei file QML
	qmlRegisterType<AppConfig>();
	qmlRegisterType<SqlManager>();
    // creo il main
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
    // Passa le proprietà dell'applicazione alla UI
    qml->setContextProperty("_helpme", this);
    // inzializzo il sqlHelper per l'accesso al DB sqlite
    SqlManager *sqlHelper = new SqlManager();
    // Inizializza il database e intercetta lo stato
    const bool dbInited = sqlHelper->initDatabase();
    // Notifica lo stato alla UI
    qml->setContextProperty("_sql", sqlHelper);
    // Creo l'oggetto root dell'applicazione
    AbstractPane *root = qml->createRootObject<AbstractPane>();
	// recupero l'oggetto MapView
	QObject* mapViewAsQObject = root->findChild<QObject*>(QString("mapViewPoiPharmacy"));
	// se l'oggetto viene trovato, crea l'oggetto che visualizza la posizione del device
	if (mapViewAsQObject) {
		// cast dell'oggetto al tipo corretto
		mapView = qobject_cast<bb::cascades::maps::MapView*>(mapViewAsQObject);
		// imposta la visibilità del tasto "Naviga To..."
		mapView->setCaptionGoButtonVisible(true);
		// se l'oggetto mapView è valido, crea il PIN che identifica 
		// la posizione del device e lo aggiunge ai dati della mappa
		if (mapView) {
			// crea l'archivio dei dati della mappa
			DataProvider* myDeviceLocDataProv = new DataProvider("my-device-location-data-provider");
			// lo aggiunge alla mappa
			mapView->mapData()->addProvider(myDeviceLocDataProv);
			// crea il bullet
			deviceLocation = new GeoLocation("current-position-id");
			deviceLocation->setName("Posizione corrente");
			deviceLocation->setDescription("Riporta la mia attuale posizione sulla mappa.");
			// Per identificare diversamente la nostra posizione
			// modifichiamo il simbolo del Marker (PIN del POI)
			Marker myPosMarker = Marker(UIToolkitSupport::absolutePathFromUrl(
									QUrl("asset:///images/my_position_mark.png")), QSize(60, 60),
									QPoint(29, 29), QPoint(29, 1));
			deviceLocation->setMarker(myPosMarker);
			myDeviceLocDataProv->add(deviceLocation);
		}
	}
    // attiva l'applicazione
	app->setScene(root);
}

/*
 * ritorna la classe AppConfig
 */
AppConfig* HelpMe::appConfig() const
{
    return m_appConfig;
}

/*
 * imposta la posizione corrente e quella del poi più vicino
 */
void HelpMe::updateDeviceLocation(double lat, double lon, double poiLat, double poiLon) {
	// imposta la mia posizione
	if (deviceLocation) {
		deviceLocation->setLatitude(lat);
		deviceLocation->setLongitude(lon);
	}
	mapView->setLatitude(lat);
	mapView->setLongitude(lon);
	// imposta la posizione del POI
	GeoLocation *pharmPos = new GeoLocation("pharmacy-position-id");
	pharmPos->setLatitude(poiLat);
	pharmPos->setLongitude(poiLon);
	pharmPos->setName("Posizione farmacia più vicina");
	pharmPos->setDescription("Farmacia di test, via della Prova 5 - Monza");
	mapView->mapData()->add(pharmPos);
}

Ed ecco il risultato finale:

MapView

Per chi desidera approfondire l’argomento, può trovare maggiori dettagli qui.

Nicola D’Amico
Italian Developer for BlackBerry® smartphones

Aggiungiamo il link a BlackBerry World nelle nostre applicazioni Cascades

Ciò che rende un’applicazione appetibile al download da parte degli utenti, oltre alla qualità e usabilità della stessa, sono sicuramente le recensioni. Un’applicazione che ha delle recensioni ha un valore aggiunto rispetto a un’applicazione che non ne ha, specie se queste sono positive. E’ anche vero purtroppo che gli utenti in genere non sono molto “portati” a lasciare il proprio giudizio sul market e quindi vanno in qualche modo invogliati a farlo.

recensione

Un modo per invogliare l’utente a scrivere una recensione è sicuramente quello di aggiungere nella nostra applicazione il link diretto a BlackBerry World in modo che l’utente con un solo click si ritrovi direttamente nella pagina del market. Potrà così scrivere la recensione senza perdere tempo in ricerche o altro.
Aggiungere questa funzionalità è inoltre molto semplice grazie a Invocation Framework; vediamo come fare.

Supponiamo di voler aggiungere questa funzionalità come “actions” nella pagina “About” della nostra applicazione. Quello che dobbiamo fare è aggiungere un “InvokeActionItem” alle nostre actions definendo correttamente la query di invoke con le seguenti tre proprietà:

- invokeTargetId: ID dell’applicazione Target, nel nostro caso “sys.appworld”
- invokeActionId: ID dell’azione da compiere, nel nostro caso “bb.action.OPEN”
- uri: URL della nostra applicazione “appworld://content/[CODICE_APPLICAZIONE_BBWORLD]“

Il nostro oggetto InvokeActionItem avrà quindi la seguente configurazione:

...
...
InvokeActionItem {
	// richiama BlackBerry World per consentire all'utente di rilasciare una recensione
    title: qsTr("Recensione")
    ActionBar.placement: ActionBarPlacement.OnBar
    imageSource: "images/ic_review_bbworld.png"
    query {
        invokeTargetId: "sys.appworld"
        invokeActionId: "bb.action.OPEN"
        // aggiungere qui il riferimento alla propria applicazione
        // esempio: uri: "appworld://content/27686800"
        //
        // Nota:
        // Assicurarsi che il CODICE_APPLICAZIONE_BBWORLD inserito sia
        // quella della propria applicazione. Per verificarlo, inserire 
        // nel link seguente il codice e provare ad aprire il 
        // nel browser del PC
        // http://appworld.blackberry.com/webstore/content/[CODICE_APPLICAZIONE_BBWORLD]
        //
        uri: "appworld://content/[CODICE_APPLICAZIONE_BBWORLD]"
    }
}
...
...

La parte più importante da configurare è il Codice identificativo della nostra applicazione su BlackBerry World (ContentID) che rappresenta la chiave utilizzata da BB World per trovare l’applicazione e visualizzarla. Il modo più veloce per identificarlo è aprire BlackBerry World sul PC, ricercare la nostra applicazione e individuare il codice nell’URL. Ad esempio, l’applicazione IBD ha un URL di questo tipo:

http://appworld.blackberry.com/webstore/content/27686800

Vediamo adesso l’esempio completo della pagina di About:

/**
* About.qml
* 
* Summary: pagina che visualizza le informazioni su applicazione, autore, 
*		   credits e accesso a BlackBerry World per recensione
* 
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
* 
* Date: 07.05.2013
* 
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
* 
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
* 
*/
import bb.cascades 1.0
import "component"

Page {
    id: root
    titleBar: TitleBar {
        title: qsTr("Informazioni su...")
    }    
    Container {
        layout: StackLayout {}
        // area scrollabile
        ScrollView {
            scrollViewProperties {
                scrollMode: ScrollMode.Vertical
                pinchToZoomEnabled: false
            }
            Container {
                topPadding: 20
                leftPadding: 10
                rightPadding: 10
                // Nome Applicazione
                Label {
                    text: qsTr("[Nome applicazione]")
                    textStyle { base: tsAboutTitleStyle.style }
                } 
                Container {
                    layout: StackLayout {
                        orientation: LayoutOrientation.LeftToRight
                    }
                    Container {
                        layoutProperties: StackLayoutProperties {
                            spaceQuota: -1
                        }
                        ImageView {
                            verticalAlignment: VerticalAlignment.Center
                            imageSource: "asset:///images/ico_app.png"
                        }                         
                    }
                    Container {
                        leftPadding: 10
                        layoutProperties: StackLayoutProperties {
                            spaceQuota: 1
                        }                         
                        verticalAlignment: VerticalAlignment.Center
                        layout: StackLayout {
                            orientation: LayoutOrientation.TopToBottom
                        }
                        // descrizione dell'applicazione
                        Label {
                            text: _appConfig.appDescription
                            multiline: true
                            textStyle { base: tsAboutBodyStyle.style }
                        }
                        Label {
                            id: versionNumber
                            objectName: "versionNumber"
                            text: qsTr("Versione: %1").arg(_appConfig.appVersion)
                            textStyle { base: tsAboutBodyStyle.style }
                        }                         
                    }
                }
                // inserisce un separatore
                Divider {}
                Container {
                    layout: StackLayout {}
	                Label {
	                    text: qsTr("Autore:")
	                    textStyle { base: tsAboutTitleStyle.style }
	                } 
                    // nominativo autore                  
                    Container {
                        verticalAlignment: VerticalAlignment.Center
                        layout: StackLayout {
                            orientation: LayoutOrientation.LeftToRight
                        }
                        Container {
                            layoutProperties: StackLayoutProperties {
                                spaceQuota: -1
                            }
                            verticalAlignment: VerticalAlignment.Center                            
                            ImageView {
                                imageSource: "asset:///images/avatar_autore.png"
                                verticalAlignment: VerticalAlignment.Center
                            }                             
                        }
                        Container {
                            leftPadding: 10
                            layoutProperties: StackLayoutProperties {
                                spaceQuota: 1
                            }
                            verticalAlignment: VerticalAlignment.Center                             
                            Label {
                                verticalAlignment: VerticalAlignment.Center
                                text: "[informazioni autore]"
                                textStyle { base: tsAboutBodyStyle.style }
                                multiline: true 
                            } 
                        }
                    }
                }
                Divider {}
                // informazioni su come contattare l'autore
                Container {
                    layout: StackLayout {}
                    Label {
                        text: qsTr("Contatta l'autore su:")
                        textStyle { base: tsAboutTitleStyle.style }
                    } 
                    // contatto email                  
                    Container {
                        layout: StackLayout {
                            orientation: LayoutOrientation.LeftToRight
                        }
                        ImageView {
                            imageSource: "asset:///images/email.png"
                        }                    
                        Label {
                            text: "[email]"
                            verticalAlignment: VerticalAlignment.Center
                            textStyle { base: tsAboutBodyStyle.style }
                        }                        
                    }
                }
                Divider {}     
                // crea una webView nella quale visualizzare la licenza
                WebView {
                    id: viewLicense
                    url: _appConfig.urlLicense
                }
            }
        }
    }
    actions: [
        InvokeActionItem {
        	// richiama BlackBerry World per consentire all'utente di rilasciare una recensione
            title: qsTr("Recensione")
            ActionBar.placement: ActionBarPlacement.OnBar
            imageSource: "images/ic_review_bbworld.png"
            query {
                invokeTargetId: "sys.appworld"
                invokeActionId: "bb.action.OPEN"
                // aggiungere qui il riferimento alla propria applicazione
                // esempio: uri: "appworld://content/27686800"
                //
                // Nota:
                // Assicurarsi che il CODICE_APPLICAZIONE_BBWORLD inserito sia
                // quella della propria applicazione. Per verificarlo, inserire 
                // nel link seguente il codice e provare ad aprire il 
                // nel browser del PC
                // http://appworld.blackberry.com/webstore/content/[CODICE_APPLICAZIONE_BBWORLD]
                //
                uri: "appworld://content/[CODICE_APPLICAZIONE_BBWORLD]"
            }
        }
    ]
}

Se non vi piace l’utilizzo dell’Invocation Framework, potete ottenere lo stesso risultato sfruttando insieme la capacità di renderizzare codice HTML all’interno dei componenti Cascades e l’utilizzo del mimetype, ad esempio in una label.

...
...
Container {
    layout: StackLayout {
        orientation: LayoutOrientation.LeftToRight
    }
    ImageView {
        imageSource: "asset:///images/ic_bbworld.png"
    }
    Label {
        text: qsTr("<a mimetype=\"application/x-bb-appworld\" 
              href=\"http://appworld.blackberry.com/webstore/content/[CODICE_APPLICAZIONE_BBWORLD]\">
              Scrivi una recensione</a>")
        verticalAlignment: VerticalAlignment.Center
        textStyle { base: tsAboutBodyStyle.style }
        textFormat: TextFormat.Html
    }
}
...
...

Per chi desidera approfondire l’argomento, può trovare maggiori dettagli qui.

Nicola D’Amico
Italian Developer for BlackBerry® smartphones

Come aggiungere il supporto a BlackBerry Messenger in una applicazione BlackBerry 10

Uno dei servizi BlackBerry più interessanti a cui legare una nostra applicazione sviluppata con gli strumenti nativi Cascades for BlackBerry 10 è sicuramente la piattaforma BlackBerry Messenger. La connessione di una applicazione a questa piattaforma consente di sfruttare pienamente le caratteristiche social di BBM, offrendo agli utenti la possibilità di condividere l’applicazione connessa con altri utenti che utilizzano BBM. Spesso gli sviluppatori tendono a non valutare questa integrazione ipotizzando che non sia utile e che in ogni caso implementare tutto questo sia complesso.

bbm_connect_home

Nella realtà connettere l’applicazione a BlackBerry Messenger è molto utile in quanto aumenta la visibilità della stessa. Inoltre non è assolutamente vero che sia complesso lo sviluppo necessario per attivare l’integrazione con BBM in una nostra applicazione. Per dimostrarlo, in questo articolo faremo vedere con un esempio pratico come connettere un’applicazione a BBM e come aggiungere la funzionalità di “Invito al Download” tramite BBM.

La prima cosa da fare è aggiungere al file .PRO questa riga “LIBS += -lbbplatformbbm” che rappresenta il riferimento per aggiungere la libreria di gestione della piattaforma BBM nel nostro progetto:

APP_NAME = app

CONFIG += qt warn_on cascades10

LIBS += -lbbdata -lbbsystem
LIBS += -lbb
LIBS += -lbbplatform
LIBS += -lbbdevice
LIBS += -lbbplatformbbm

include(config.pri)
...
...

Aggiungiamo nel file “bar-descriptor.xml“, nella sezione Permission, l’autorizzazione a connettersi al BBM inserendo la chiave “bbm_connect”.

...
...
<permission system="true">run_native</permission>
<permission>access_internet</permission>
<permission>bbm_connect</permission>
...
...

Fatto questo, costruiamo la classe “RegistrationHandler” che servirà per poter registrare l’applicazione alla piattaforma BlackBerry Messenger. Di questa classe la parte più importante sono i metodi “checkRegistration()” e “registerApplication()” che si occupano rispettivamente di verificare lo stato della registrazione e attivare il processo di registrazione dell’applicazione a BBM.

...
...

/**
 * checkRegistration(): verifica lo stato delle registrazione a BBM
 */
void RegistrationHandler::checkRegistration()
{
	// imposta la modalità di check registrazione
	m_isStartReg = false;
	m_progress = BbmRegistrationProgress::Started;
	processRegistrationStatus(m_context.registrationState());
}

/**
 * registerApplication(): registrazione a BBM
 */
void RegistrationHandler::registerApplication()
{
	// imposta la modalità di registrazione
	m_isStartReg = true;
    m_progress = BbmRegistrationProgress::Started;
    processRegistrationStatus(m_context.registrationState());
}

...
...

Il processo di registrazione viene gestito attraverso l’evento registrationStateUpdated che viene connesso allo Slot processRegistrationStatus alla creazione della classe. Il processo da utilizzare viene attivato valutando l’Enum dello stato della registrazione “BbmRegistrationProgress”.

...
...
    connect(&m_context, SIGNAL(registrationStateUpdated(
                   bb::platform::bbm::RegistrationState::Type)),
            this, SLOT(processRegistrationStatus(
                 bb::platform::bbm::RegistrationState::Type)));
...
...
void RegistrationHandler::processRegistrationStatus(const RegistrationState::Type state)
{
		...
		...
	    case BbmRegistrationProgress::Started:
	    	// se devo solo verificare e l'applicazione non è registrata notifica lo stato
	    	if (!m_isStartReg && m_context.registrationState() == RegistrationState::Unregistered) {
	            // Access is allowed, the application is registered.
	            registrationFinished();
	            return;
	    	}
		 	...
			...
	        // Inizia la registrazione su BBM
	        if (m_context.requestRegisterApplication()) {
	            m_progress = BbmRegistrationProgress::Pending;
	            return;
	        }
			...
			...
    }
}

Vediamo i sorgenti completi della classe:

/**
* RegistrationHandler.hpp
*
* Summary: header della classe RegistrationHandler.cpp
*
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
*
* Date: 01.05.2013
*
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
*
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
#ifndef REGISTRATIONHANDLER_HPP
#define REGISTRATIONHANDLER_HPP

#include <bb/platform/bbm/Context>
#include <bb/platform/bbm/RegistrationState>
#include <bb/system/SystemUiResult>

#include <QtCore/QObject>
#include <QUuid>

class RegistrationHandler : public QObject
{
    Q_OBJECT

    // Flag che indica se l'applicazione è registrata con successo con BBM.
    Q_PROPERTY(bool allowed READ isAllowed NOTIFY stateChanged)

    // Messaggio di stato che descrive il processo di registrazione.
    Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY stateChanged)

    // Flag che indica se la registrazione non è riuscita a causa di un errore temporaneo.
    // Questo permette all'utente di riprovare la registrazione.
    Q_PROPERTY(bool temporaryError READ isTemporaryError NOTIFY stateChanged)

public:
    // I possibili stati della registrazione
    struct BbmRegistrationProgress
    {
        enum Type {
            NotStarted = 0,
            Started,
            Pending,
            Finished
        };
    };

    /**
     * Costruttore della classe
     */
    RegistrationHandler(const QUuid &uuid, QObject *parent = 0);

    /**
     * Restituisce il context() BBM che è associato all'applicazione
     */
    bb::platform::bbm::Context& context()
    { return m_context; }

    /**
     * Returns la situazione della registrazione
     */
    BbmRegistrationProgress::Type progress() const
    { return m_progress; }

public Q_SLOTS:

	// attiva il processo di registrazione
    void registerApplication();
    // attiva il processo di verifica della registrazione
    void checkRegistration();

Q_SIGNALS:
    // notifica il cambio dello stato della registrazione
    void stateChanged();

private Q_SLOTS:

	// invocato quando lo stato della registrazione cambia
    void processRegistrationStatus(const bb::platform::bbm::RegistrationState::Type state);

	// invocato quando il valore di UUID non è valido
    void dialogFinished(bb::system::SystemUiResult::Type value);

private:
    // Restituisce vero se la registrazione è stata completata con successo
    bool isAllowed() const
    { return m_isAllowed; }
    // Restituisce vero se la registrazione non è riuscita a causa di un errore temporaneo
    bool isTemporaryError() const
    { return m_temporaryError; }
    // Ritorna il messaggio che descrive lo stato di registrazione
    const QString& statusMessage() const
    { return m_statusMessage; }
    // Registrazione terminata
    void registrationFinished();
    // BBM Social Platform Context utilizzato per accedere alle funzionalità BBM
    bb::platform::bbm::Context m_context;
    // Flag che indica se la registrazione completata con successo
    bool m_isAllowed;
	// Progresso della registrazione progresso. 
    BbmRegistrationProgress::Type m_progress;
    // Flag che indica se la registrazione non è riuscita a causa di un errore temporaneo
    bool m_temporaryError;
    // Messaggio che descrive lo stato di registrazione
    QString m_statusMessage;
	// Flag che indica se la registrazione deve essere avviata
    bool m_isStartReg;
};

#endif
/**
* RegistrationHandler.cpp
*
* Summary: classe utilizzata per gestire la connessione al BBM
*
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
*
* Date: 01.05.2013
*
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
*
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
#include "RegistrationHandler.hpp"

#include <bb/cascades/AbstractPane>
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/system/SystemDialog>

#include <bb/platform/bbm/Context>
#include <bb/platform/bbm/RegistrationState>

using namespace bb::cascades;
using namespace bb::platform::bbm;
using namespace bb::system;

RegistrationHandler::RegistrationHandler(const QUuid &uuid, QObject *parent)
    : QObject(parent)
    , m_context(uuid)
    , m_isAllowed(false)
    , m_progress(BbmRegistrationProgress::NotStarted)
    , m_temporaryError(false)
    , m_statusMessage(tr("Attendere che l'applicazione si connetta a BBM"))
{
    if (uuid.isNull()) {
    	// mostra una dialogbox per informare l'utente che l'applicazione non ha un
    	// valore di UUID valido
    	SystemDialog *uuidDialog = new SystemDialog(tr("OK"));
    	uuidDialog->setTitle(tr("UUID Errato!"));
        uuidDialog->setBody(tr("UUID non valido o nullo, contattare lo sviluppatore."));
        connect(uuidDialog, SIGNAL(finished(bb::system::SystemUiResult::Type)), this, SLOT(dialogFinished(bb::system::SystemUiResult::Type)));
        uuidDialog->show();
        return;
    }
    connect(&m_context,
            SIGNAL(registrationStateUpdated(
                   bb::platform::bbm::RegistrationState::Type)),
            this,
            SLOT(processRegistrationStatus(
                 bb::platform::bbm::RegistrationState::Type)));
}

/**
 * checkRegistration(): verifica lo stato delle registrazione a BBM
 */
void RegistrationHandler::checkRegistration()
{
	// imposta la modalità di check registrazione
	m_isStartReg = false;
	m_progress = BbmRegistrationProgress::Started;
	processRegistrationStatus(m_context.registrationState());
}

/**
 * registerApplication(): registrazione a BBM
 */
void RegistrationHandler::registerApplication()
{
	// imposta la modalità di registrazione
	m_isStartReg = true;
    m_progress = BbmRegistrationProgress::Started;
    processRegistrationStatus(m_context.registrationState());
}

void RegistrationHandler::processRegistrationStatus(const RegistrationState::Type state)
{
	// In base allo stato, viene valutato se bisogna registrarsi. Se siamo già registrati
    // l'utente riceve come notifica il fatto che l'applicazione è già registrata sulla
    // piattaforma BBM
    switch(m_progress)
    {
	    case BbmRegistrationProgress::Pending:
	        if (state != RegistrationState::Pending) {
	            registrationFinished();
	            return;
	        }
	        break;
	    case BbmRegistrationProgress::Started:
	    	// se devo solo verificare e l'applicazione non è registrata notifica lo stato
	    	if (!m_isStartReg && m_context.registrationState() == RegistrationState::Unregistered) {
	            // Access is allowed, the application is registered.
	            registrationFinished();
	            return;
	    	}
	    	// Registrazione completata
	        if (m_context.isAccessAllowed()) {
	            registrationFinished();
	            return;
	        }
	        // stato della registrazione sconosciuto
	        if (m_context.registrationState() == RegistrationState::Unknown) {
	            return;
	        }
	        // Inizia la registrazione su BBM
	        if (m_context.requestRegisterApplication()) {
	            m_progress = BbmRegistrationProgress::Pending;
	            return;
	        }
	        registrationFinished();
	        break;
	    case BbmRegistrationProgress::Finished:
	        if (m_context.isAccessAllowed() != m_isAllowed) {
	            registrationFinished();
	        }
	        break;
    }
}

void RegistrationHandler::registrationFinished()
{
	// In base allo stato delle registrazione visualizzo all'utente l'informazione corretta
    m_progress = BbmRegistrationProgress::Finished;
    switch (m_context.registrationState()) {
    case RegistrationState::Allowed:
        m_statusMessage = tr("Applicazione connessa a BBM. Premere Chiudi.");
        m_temporaryError = false;
        break;
    case RegistrationState::BlockedByRIM:
        m_statusMessage = tr("Disconnesso da RIM. L'applicazione è stata bloccata da BlackBerry e non può essere connessa a BBM");
        m_temporaryError = false;
        break;
    case RegistrationState::BlockedByUser:
        m_statusMessage = tr("Non connesso. Aprire Impostazione -> Protezione e privacy -> Autorizzaz. applicazioni e abilitare la connessione a BBM.");
        m_temporaryError = false;
        break;
    case RegistrationState::InvalidUuid:
        m_statusMessage = tr("UUID non valido. Contattare lo sviluppatore.");
        m_temporaryError = true;
        break;
    case RegistrationState::MaxAppsReached:
        m_statusMessage = tr("Troppe applicazioni connesse a BBM. Disconnettere una o più applicazioni da BBM e riprovare.");
        m_temporaryError = false;
        break;
    case RegistrationState::Expired:
    case RegistrationState::MaxDownloadsReached:
        m_statusMessage = tr("Impossibile connettersi a BBM. Scarica questa applicazione da AppWorld per continuare a utilizzarla.");
        m_temporaryError = false;
        break;
    case RegistrationState::NoDataConnection:
        m_statusMessage = tr("Controlla la tua connessione a Internet e riprova.");
        m_temporaryError = true;
        break;
    case RegistrationState::Pending:
        m_statusMessage = tr("Collegamento a BBM. Si prega di attendere.");
        m_temporaryError = false;
        break;
    case RegistrationState::Unknown:
        m_statusMessage = tr("Verifica stato in corso, attendere prego...");
        m_temporaryError = false;
        break;
    case RegistrationState::Unregistered:
    case RegistrationState::UnexpectedError:
    case RegistrationState::TemporaryError:
    case RegistrationState::CancelledByUser:
    default:
        m_statusMessage = tr("Volete collegare l'applicazione a BBM?");
        m_temporaryError = true;
        break;
    }
    if (m_context.isAccessAllowed()) {
        m_isAllowed = true;
    } else {
        m_isAllowed = false;
    }
    emit stateChanged();
}

void RegistrationHandler::dialogFinished(bb::system::SystemUiResult::Type value) {
	Application::exit(-1);
}

Costruiamo adesso la classe “InviteToDownload” che servirà per sfruttare il servizio di messagistica della piattaforma BlackBerry Messenger per notificare l’invito a scaricare l’applicazione ad un utente BBM. Per fare questo è sufficiente istanziare il servizio di messaggistica e richiamare il metodo “sendDownloadInvitation()”.

...
...
void InviteToDownload::sendInvite()
{
	// se il servizio BBM MessageService non è istanziato, provvede a farlo
    if (!m_messageService) {
        m_messageService = new bb::platform::bbm::MessageService(m_context, this);
    }
    // richiama la card per l'invio dell'invito
    m_messageService->sendDownloadInvitation();
}
...
...

Vediamo i sorgenti completi della classe:

/**
* InviteToDownload.hpp
*
* Summary: header della classe InviteToDownload.cpp
*
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
*
* Date: 01.05.2013
*
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
*
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
#ifndef INVITETODOWNLOAD_HPP
#define INVITETODOWNLOAD_HPP

#include <QtCore/QObject>

namespace bb { namespace platform { namespace bbm { class Context; class MessageService; }}}

class InviteToDownload : public QObject
{
    Q_OBJECT

public:
    /**
     * Crea un nuovo oggetto InviteToDownload
     */
    InviteToDownload(bb::platform::bbm::Context &context, QObject *parent = 0);

    // Questo metodo viene invocato per aprire la finestra di invito
    Q_INVOKABLE void sendInvite();

private:
    // Oggetti per inviare messaggi BBM
    bb::platform::bbm::MessageService* m_messageService;
    bb::platform::bbm::Context* m_context;
};

#endif
/**
* InviteToDownload.cpp
*
* Summary: classe per la gestione dell'invito a scaricare l'applicazione attraverso BBM
*
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
*
* Date: 01.05.2013
*
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
*
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
#include "InviteToDownload.hpp"
#include "RegistrationHandler.hpp"

#include <bb/cascades/AbstractPane>
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>

#include <bb/platform/bbm/MessageService>

using namespace bb::cascades;

InviteToDownload::InviteToDownload(bb::platform::bbm::Context &context, QObject *parent)
    : QObject(parent)
    , m_messageService(0)
    , m_context(&context)
{
}

void InviteToDownload::sendInvite()
{
	// se il servizio BBM MessageService non è istanziato, provvede a farlo
    if (!m_messageService) {
        m_messageService = new bb::platform::bbm::MessageService(m_context, this);
    }
    // richiama la card per l'invio dell'invito
    m_messageService->sendDownloadInvitation();
}

Preparate le classi “RegistrationHandler” e “InviteToDownload”, non rimane che integrarne l’utilizzo nella nostra applicazione. Per fare questo, aggiungiamo nella classe “applicationui.cpp” le seguenti righe:

...
...
#include "applicationui.hpp"
#include "RegistrationHandler.hpp"
#include "InviteToDownload.hpp"
...
...
#include <bb/system/SystemDialog>
#include <bb/platform/bbm/MessageService>

using namespace bb::cascades;
using namespace bb::data;
using namespace bb::system;

ApplicationUI::ApplicationUI(bb::cascades::Application *app)
: QObject(app)
{
	...
	...
    qmlRegisterType<SystemDialog>("my.systemDialogs", 1, 0, "SystemDialog");
	...
	...
    // Andare sul sito http://www.guidgenerator.com/
    // generare un valore UUID e sostituire "XXXXXXXXXXXXXXXXXXXXX" con questo valore
    // il valore UUID rappresenta una chiave univoca che individua la vostra 
    // applicazione sulla piattaforma BBM
    const QUuid uuid(QLatin1String("XXXXXXXXXXXXXXXXXXXXX"));
    // Istanziamo la classe di registrazione a BBM
    RegistrationHandler *registrationHandler = new RegistrationHandler(uuid, this);
	// rendiamo disponibile la classe nei file QML
    qml->setContextProperty("_registrationHandler", registrationHandler);
	// instanziamo la classe per gestire gli inviti al download
    InviteToDownload *bbmInvite = new InviteToDownload(registrationHandler->context(), this);
    // rendiamo disponibile la classe nei file QML
    qml->setContextProperty("_bbmInvite", bbmInvite);
    ...
    ...
    // create root object for the UI
    AbstractPane *root = qml->createRootObject<AbstractPane>();
    // set created root object as a scene
    app->setScene(root);
}

Creiamo una pagina che sarà visualizzata all’utente quando attiviamo la registrazione dell’applicazione a BBM.

/**
* ConnectToBBM.qml
*
* Summary: Pagina che consente la registrazione a BBM
*
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
*
* Date: 01.05.2013
*
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
*
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
import bb.cascades 1.0

Page {
    id: page
    Container {
        onCreationCompleted: {
            _registrationHandler.checkRegistration();
        }
        layout: StackLayout {}
        background: Color.create("#363333")
        ImageView {           
            imageSource: _appConfig.bbmHeaderImage
        }
        Container {
            topPadding: 40
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
            // visualizza lo stato delle connessione a BBM
            Label {
                id: statusLabel
                horizontalAlignment: HorizontalAlignment.Center
                text: _registrationHandler.statusMessage
                textStyle {
                    base: tsBbmStyle.style
                }
                multiline: true
            }
            Container {
                preferredHeight: 20
            }
            // pulsate che abilita alla registrazione su BBM
            Button {
                horizontalAlignment: HorizontalAlignment.Center
                visible: _registrationHandler.temporaryError
                text: qsTr("Connetti a BBM")
                onClicked: {
                    _registrationHandler.registerApplication()
                }
            }
        }
    }
    actions: [
        ActionItem {
            // chiude il form di registrazione a BBM
            title: qsTr("Chiudi")
            ActionBar.placement: ActionBarPlacement.OnBar
            imageSource: "asset:///images/ic_cancel.png"
            onTriggered: {
                connectToBBMSheet.close();
            }
        }
    ]
}

In questo esempio si è considerato di rendere la registrazione manuale, quindi solo su richiesta dell’utente che utilizza l’applicazione. Se si vuole effettuare la registrazione automaticamente al primo avvio dell’applicazione, è sufficiente sostituire la chiamata dell’evento “onCreationCompleted” con il seguente:

...
...
onCreationCompleted: {
    _registrationHandler.registerApplication();
}
...
...

A questo punto non resta che aggiungere nel file main.qml i riferimenti per la gestione del testo e la creazione della “Sheet”.

...
...
    attachedObjects: [
    	...
    	...
        Sheet {
            // sheet utilizzata quando l'utente seleziona di connettere l'applicazione a BBM
            // la sheet viene creata subito, quindi appena creata viene eseguito il test
            // per verificare se l'utente è connesso a BBM
            id: connectToBBMSheet
            objectName: "connectToBBMSheet"
            ConnectToBBM {
            }
        },
        TextStyleDefinition {
            id: tsBbmStyle
            color: Color.create("#b1b1b1")
            fontSize: FontSize.PointValue
            fontSizeValue: 7
            fontStyle: FontStyle.Normal
            fontWeight: FontWeight.Normal
        },
        ...
        ...
    ]
...
...

Infine, non resta che aggiungere alla nostra applicazione dove richiamare la pagina di connessione al BBM e l’invito al download. In questo esempio, supponiamo di aggiungerlo come ActionItem.

...
...
    actions: [
         ActionItem {
            id: registerToBBM
            ActionBar.placement: ActionBarPlacement.InOverflow
            title: qsTr("Connetti a BBM")
            imageSource: "asset:///images/ic_bbm_connect.png"
            onTriggered: {
                // apre la pagina per connettere l'applicazione al BBM
                connectToBBMSheet.open();
            }
        },
        ActionItem {
            id: bbmInviteToDownload
            ActionBar.placement: ActionBarPlacement.InOverflow
            title: qsTr("Invita al download")
            enabled: _registrationHandler.allowed
            imageSource: "asset:///images/ic_bbm_invite.png"
            onTriggered: {
                _bbmInvite.sendInvite();
            }
        }
	]
...
...

Ecco in ultimo il risultato finale.

bbm_connecthome_share

Per chi desidera approfondire l’argomento, può trovare maggiori dettagli qui.

Nicola D’Amico
Italian Developer for BlackBerry® smartphones

Come costruire in modo dinamico la query di Invoke

Una delle potenzialità del nuovo BlackBerry OS 10 è l’Invocation framework, ossia la possibilità di richiamare un’applicazione nativa o di terze parti passando alla stessa una query che indica l’azione da compiere.
Questa funzione ha molte potenzialità, specie se la query viene costruita in modo dinamico in base alle necessità.
Molti sviluppatori hanno avuto difficoltà nell’utilizzo dinamico in quanto il settaggio della proprietà “uri” della query dall’esterno della pagina QML in cui è usata, a volte non funziona correttamente.

invoke

Questo comportamento che viene interpretato come anomalo, in realtà è del tutto corretto ed è dovuto al fatto che quando la pagina QML viene creata tutte le proprietà non sono ancora valorizzate. Esiste però la possibiltà di superare questo ostacolo in modo molto semplice. Per vedere come, supponiamo di avere un’applicazione che visualizza una lista di feed rss e alla selezione di un elemento della lista, desideriamo visualizzare una pagina di dettaglio del feed e contestualmente vogliamo impostare un InvokeActionItem che, qualora l’utente lo selezioni, apra l’articolo nel browser del dispositivo.

Per aprire il browser del dispositivo dalla nostra applicazione, dobbiamo costruire un InvokeActionItem in cui:

- il TargetID sia il Browser
- l’ActionID sia la Open

Il nostro InvokeActionItem avrà quindi la seguente forma:

...
...
InvokeActionItem {
    id: openFeedInBrowser
    ActionBar.placement: ActionBarPlacement.InOverflow
    title: qsTr("Leggi articolo originale")
    query {
        invokeTargetId: "sys.browser"
        mimeType: "text/plain"
        invokeActionId: "bb.action.OPEN"
        uri: "http://italianblackberrydev.wordpress.com/"
    }
}
...
...

Con il codice appena visto, abbiamo definito un InvokeActionItem la cui selezione aprirà nel browser del dispositivo l’indirizzo web indicato nella proprietà “uri”. Per rendere dinamica l’impostazione della proprietà “uri”, aggiugiamo i seguenti pezzi di codice:

...
...
Page {
    id: detailsViewPage
 	...
 	...
    property string link: ""
    Container {
    	...
    	...
    }
    actions: [
    	...
    	...
        InvokeActionItem {
            id: openFeedInBrowser
            ActionBar.placement: ActionBarPlacement.InOverflow
            title: qsTr("Leggi articolo originale")
            query {
                invokeTargetId: "sys.browser"
                mimeType: "text/plain"
                invokeActionId: "bb.action.OPEN"
                uri: "http://italianblackberrydev.wordpress.com/"
                onUriChanged: {
                    openFeedInBrowser.query.updateQuery();
                }
            }
        }
        ...
        ...
    ]
    onLinkChanged: {
        openFeedInBrowser.query.uri = link;
    }
}

Abbiamo definito una proprietà a livello di pagina QML che abbiamo chiamato “link” che sarà valorizzata dalla pagina chiamante. Abbiamo aggiunto l’evento “onUriChanged” che viene scatenato quando la proprietà “uri” viene modificata. Abbiamo creato un evento “onLinkChanged” legato alla proprietà “link” che viene scatenato quando la proprietà è valorizzata dalla pagina chiamante. Al verificarsi di questo evento, sarà impostata la proprietà “uri” con il valore della proprietà “link”. Questa modifica scatena l’evento “onUriChanged” che a sua volta forza l’aggiornamento della query di invoke. Abbiamo in questo modo impostato dinamicamente la la chiamata del browser.

Vediamo adesso il codice completo.

Pagina FeedPage.qml:

/**
* FeedPage.qml
* 
* Summary: Visualizza una lista di Feed RSS
* 
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
* 
* Date: 25.04.2013
* 
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
* 
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
import bb.cascades 1.0
import bb.data 1.0
import nd.image 1.0
import nd.systemDialogs 1.0

Page {
	// proprietà alias
    property alias feedlink: rssSource.source
    property alias title: titleBar.title
    titleBar: TitleBar {
        id: titleBar
    }
    Container {
        layout: DockLayout {
        }
        // imposta un background come sfondo
        background: backgroundBox.imagePaint
        // crea un activityIndicator
        ActivityIndicator {
            id: dataLoadIndicator
            preferredWidth: 400
            preferredHeight: 400
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Top
        }
        ListView {
            id: feedsList
            dataModel: feedModel
            layout: StackListLayout {
                headerMode: ListHeaderMode.Sticky
            }
            listItemComponents: [
                ListItemComponent {
                    type: "textItem"
                    TextItem {
                    }
                },
                ListItemComponent {
                    type: "header"
                    Header {
                        title: if (ListItemData.toDateString() != undefined) ListItemData.toDateString()
                        subtitle: (ListItem.initialized ? ListItem.view.dataModel.childCount(ListItem.indexPath) : 0)
                    }
                }
            ]
            onTriggered: {
            	// recupera l'elemento selezionato
                var itemSelected = dataModel.data(indexPath);
                // crea la pagina di dettaglio
                var page = detailsView.createObject();
                // Imposta le proprietà
                page.htmlContent = itemSelected["content:encoded"];
                // quando imposta il link si scatena l'evento onLinkChanged della pagina DetailsView.qml
                page.link = itemSelected.link;
                page.titleFeed = itemSelected.title;
                nav.push(page);
            }
        }
        attachedObjects: [
            GroupDataModel {
                id: feedModel
                sortingKeys: ["pubDate"]
                sortedAscending: false
                grouping: ItemGrouping.ByFullValue
            },
            DataSource {
                id: rssSource
                // impostazione della query da eseguire sul Feed RSS per recuperare la lista degli item
                query: "/rss/channel/item"
                onDataLoaded: {
                    // pulisce il dataModel
                    feedModel.clear();
                    // popola la lista
                    feedModel.insertList(data);
                    // blocca l'activityIndicator
                    dataLoadIndicator.stop();
                }
                onError: {
                	// visualizza un errore
                    dataLoadIndicator.stop();
                    errorDialog.show();
                }
                onSourceChanged: {
                    feedModel.clear();
                    rssSource.load();
                }
            },
            // costruisce la dialogBox che visualizzerà l'eventuale errore
            SystemDialog {
                id: errorDialog
                title: "Errore!!!"
                body: "Impossibile recuperare i dati per aggiornare la lista"
            }
        ]
        onCreationCompleted: {
            dataLoadIndicator.start();
        }
    }
    actions: [
        ActionItem {
            id: updateRSS
            ActionBar.placement: ActionBarPlacement.InOverflow
            title: qsTr("Aggiorna")
            imageSource: "asset:///images/ic_help.png"
            onTriggered: {
                dataLoadIndicator.start();
                feedModel.clear();
                rssSource.load();
            }
        },
        InvokeActionItem {
            ActionBar.placement: ActionBarPlacement.InOverflow
            title: qsTr("Apri nel Browser")
            query {
                invokeTargetId: "sys.browser"
                mimeType: "text/plain"
                invokeActionId: "bb.action.OPEN"
                uri: "http://italianblackberrydev.wordpress.com/"
            }
        },
        InvokeActionItem {
            id: shareLinkSito
            ActionBar.placement: ActionBarPlacement.InOverflow
            title: qsTr("Condividi Sito")
            query {
                mimeType: "text/plain"
                invokeActionId: "bb.action.SHARE"
            }
            onTriggered: {
                data = qsTr("Visita IBD - Italian Developers resources a questo indirizzo: http://italianblackberrydev.wordpress.com/")
            }
        }
    ]
}

Pagina FeedPage.qml:

/**
* DetailsView.qml
* 
* Summary: Pagina di dettaglio del conenuto del Feed
* 
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
* 
* Date: 25.04.2013
* 
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
* 
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
import bb.cascades 1.0
import bb.data 1.0
import "component"

Page {
    id: detailsViewPage
    property alias htmlContent: viewFeedContent.html
    property alias titleFeed: feedTitle.text
    property string link: ""
    Container {
        layout: StackLayout {
            orientation: LayoutOrientation.TopToBottom
        }
        background: Color.White
        Container {
            id: detailsViewContainer
            leftPadding: 20
            rightPadding: 20
            layout: StackLayout {
            }
            background: Color.White
            ScrollView {
                id: scrollViewContent
                scrollViewProperties.pinchToZoomEnabled: false
                scrollViewProperties.scrollMode: ScrollMode.Vertical
                Container {
                    id: webContainer
                    layout: StackLayout {
                        orientation: LayoutOrientation.TopToBottom
                    }
                    Label {
                        id: feedTitle
                        textStyle { base: labelBoldStyle.style }
                        multiline: true
                    }
                    DividerDottedField {
                    }
                    WebView {
                        id: viewFeedContent
                    }
                }
            }
        }
    }
    actions: [
        InvokeActionItem {
            id: openFeedInBrowser
            ActionBar.placement: ActionBarPlacement.InOverflow
            title: qsTr("Leggi articolo originale")
            query {
                invokeTargetId: "sys.browser"
                mimeType: "text/plain"
                invokeActionId: "bb.action.OPEN"
                uri: "http://italianblackberrydev.wordpress.com/"
                onUriChanged: {
                    openFeedInBrowser.query.updateQuery();
                }
            }
        },
        InvokeActionItem {
            id: shareLinkSito
            ActionBar.placement: ActionBarPlacement.InOverflow
            title: qsTr("Condividi l'articolo")
            query {
                mimeType: "text/plain"
                invokeActionId: "bb.action.SHARE"
            }
            onTriggered: {
                data = qsTr("Leggi l'articolo '%1' su IBD a questo indirizzo: %2").arg(feedTitle).arg(link)
            }
        }
    ]
    onLinkChanged: {
        openFeedInBrowser.query.uri = link;
    }
}

Per chi desidera approfondire l’argomento, può trovare maggiori dettagli qui.

Nicola D’Amico
Italian Developer for BlackBerry® smartphones

Come eseguire il download di un file utilizzando QML e C++

Quando sviluppiamo applicazioni mobile, ci si può trovare di fronte alla necessità di dover scaricare dei file sul dispositivo. Supponiamo ad esempio di dover realizzare un’applicazione che consenta all’utente di consultare sul proprio dispositivo mobile un catalogo prodotti e di dover aggiornare periodicamente questo catalogo.
Il sistema migliore per realizzarla, è fare in modo che sia l’applicazione stessa che controlli e scarichi periodicamente sul dispositivo l’ultimo catalogo disponibile.

schema

Vediamo come realizzare in pratica questa funzionalità utilizzando QML e C++. Come prima cosa, realizziamo in QML la pagina che si occuperà di visualizzare la lista dei prodotti, avendo cura di assegnare a tutti i componenti utilizzati la proprietà “objectName“.

/**
* main.qml
* 
* Summary: pagina che visualizza la lista dei prodotti
* 
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
* 
* Date: 21.04.2013
* 
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
* 
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
import bb.cascades 1.0
 
Page {
    Container {
        id: root
        background: Color.Black
        // visualizza l'activity indicator durante il download del file
        Container {
            id: boxPleaseWait
            background: Color.White
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Top
            layout: StackLayout {
                orientation: LayoutOrientation.LeftToRight
            }
            preferredWidth: _appConfig.DisplayHeight
            leftPadding: 50
            topPadding: 50
            // crea la label che visualizza il messaggio di attesa
            Label {
                objectName: "lblAttenderePrego"
                text: "Scarica la lista dei prodotti..."
                textStyle.textAlign: TextAlign.Right
                verticalAlignment: VerticalAlignment.Center
                textStyle.color: Color.create("#00a8df")
            }
			// crea un activityIndicator per indicare l'attesa
            ActivityIndicator {
                objectName: "pleaseWaitIndicator"
                running: true
            }
        }
        Container {
            id: listViewProdotti
            background: Color.White
             // 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++ code.
            ListView {
                objectName: "prodottiListView"
 				// XmlDataModel popolato con il file XML della lista prodotti scaricati
                dataModel: XmlDataModel {
                }
                // definisce la lista costituita da un header e dalla lista dei prodotti
                listItemComponents: [
                    ListItemComponent {
                    	// header visualizza un titolo e il numero dei prodotti presenti nella lista
                        type: "header"
                        Header {
                         title: ListItemData.title
                         // uso l'operatore ternario per gestire il caso di lista vuota nel count degli 
                         // elementi della lista
                         subtitle: (ListItem.initialized ? ListItem.view.dataModel.childCount(ListItem.indexPath):0)
                       }
                    },
                    ListItemComponent {
                    	// item che visualizza i prodotti
                        type: "products"
                        StandardListItem {
                            title: ListItemData.name
                        }
                    }
                ]
            }
        }
    }
    actions: [
        ActionItem {
        	id: downloadFileAction
            objectName: "downloadFileAction"
            title: "Scarica"
            onTriggered: {
            	_appConfig.initiateRequest()
            }
            ActionBar.placement: ActionBarPlacement.OnBar
        }
    ]
}

Fatto questo, dobbiamo realizzare la parte in C++ che si occuperà di comunicare con la pagina QML e che attiverà il download del file dei prodotti. Per realizzare la comunicazione tra C++ e QML, dobbiamo come prima cosa caricare il file main.qml e recuperare il nodo principale.

...
...
// Crea il QML document di root di avvio
QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
// espone la classe "this" per le chiamate da codice C++
qml->setContextProperty("_appConfig", this);
// Crea l'oggetto per la generazione della UI
AbstractPane *root = qml->createRootObject<AbstractPane>();
...
...

Una volta che abbiamo il nodo principale, possiamo utilizzare il metodo “findChild()” per recuperare i riferimenti ai componenti della pagina QML. così possiamo chiamare le loro funzioni dal codice C++. Ciò è particolarmente utile per avviare e arrestare ad esempio l’indicatore di attività oppure per caricare il nuovo modello dati nella lista.

...
...
// recupera gli oggetti dal file QML per la gestione all'interno del codice C++
// utilizzando il metodo "findChild"
pleaseWaitActIndicator = root->findChild<ActivityIndicator*>("pleaseWaitIndicator");
listViewProdotti = root->findChild<ListView*>("prodottiListView");
labelAttenderePrego = root->findChild<Label*>("lblAttenderePrego");
actionDownload = root->findChild<ActionItem*>("downloadFileAction");
...
...

Oltre alla necessità del C++ di comunicare con QML, abbiamo bisogno della comunicazione inversa, per fare in modo che dal QML si possa ad esempio richiamare la funzionalità di aggiornamento del file prodotti. Per fare questo, usiamo il metodo “setContextProperty()” per esporre l’oggetto applicazione al QML.

...
...
// espone la classe "this" per le chiamate da codice C++
qml->setContextProperty("_appConfig", this);
...
...

Fatto questo, impostiamo l’oggetto QNetworkAccessManager il cui compito è quello di inviare le richieste di rete e gestire le risposte che vengono restituite. Per impostare l’oggetto QNetworkAccessManager, chiamiamo semplicemente il suo costruttore e successivamente colleghiamo l’evento di risposta “finished” (attivato una volta che la richiesta di rete è completa) con una nostra gestione personalizzata.

...
...
// crea l'oggetto "httpConnectionManager" di tipo "QNetworkAccessManager" per 
// gestire la connessione
httpConnectionManager = new QNetworkAccessManager(this);
// connette al metodo "requestFinished" all'evento "finished" che viene
// scatenato quando il download è stato completato
bool result = connect(httpConnectionManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
...
...

A questo punto possiamo creare il metodo che sarà richiamato dal QML per attivare il download del file. La prima cosa da fare è avviare il componente e rendere visibile l’etichetta di aggiornamento, quindi impostare il testo e il colore dei caratteri.

...
...
// attiva l'activityIndicator e visualizza la label con il messaggio di attesa
pleaseWaitActIndicator->start();
labelAttenderePrego->setVisible(true);
// assegna alla label il messaggio di attesa e imposta lo stile
labelAttenderePrego->setText("Scarica la lista dei prodotti...");
labelAttenderePrego->textStyle()->setColor(Color::DarkRed);
...
...

Successivamente, creiamo una richiesta http utilizzando la classe QNetworkRequest, impostiamo l’URL per la richiesta quindi la avviamno chiamando il metodo “get()“, che rappresenta una richiesta HTTP GET.

...
...
// crea l'oggetto per gestire la richiesta http utilizzando la classe QNetworkRequest
QNetworkRequest request = QNetworkRequest();
// imposta l'URL della richiesta
request.setUrl(QUrl("http://repository.mycompany.com/db/products/products_in_stock.xml"));
// esegue la richiesta http
httpConnectionManager->get(request);
...
...

Infine, non rimane che realizzare il metodo personalizzato per gestire la risposta di rete. Il primo passo è quello di verificare la risposta di rete per eventuali errori analizzando il valore “QNetworkReply::NoError“. Se non ci sono errori, apriamo il nostro file e scriviamo in esso i dati ricevuti come risposta dalla rete.

...
...
if (!productsFile->open(QIODevice::ReadWrite))
{
	// system dialog
    messageErrorFileOpen.show();
    return;
}
// scrive il file con il contenuto scaricato
productsFile->write(reply->readAll());
productsFile->flush();
productsFile->close();
...
...

Non rimane che assegnare il nuovo file come sorgente dati della nostra lista.

...
...
XmlDataModel *dataModel = new XmlDataModel();
dataModel->setSource(QUrl("file://" + QDir::homePath() + "/products_in_stock.xml"));
// assegna il nuovo dataModel alla lista
listViewProdotti->setDataModel(dataModel);
...
...

Vediamo adesso il codice completo in C++

/**
* ProductList.h
* 
* Summary: header file della classe ProductList.cpp
* 
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
* 
* Date: 21.04.2013
* 
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
* 
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
#ifndef ProductList_H_
#define ProductList_H_
 
#include <QObject>
#include <QFile>
#include <QtNetwork>
#include <QString>
 
#include <bb/cascades/ActivityIndicator>
#include <bb/cascades/ListView>
#include <bb/cascades/Image>
#include <bb/cascades/ImageView>
#include <bb/cascades/Label>
#include <bb/cascades/ActionItem>
 
#include <bb/AbstractBpsEventHandler>
 
namespace bb {namespace cascades {class Application;}}
 
class ProductList : public QObject
{
    Q_OBJECT
 
public:
    productList(bb::cascades::Application *app);
    virtual ~ProductList(){}
 
    Q_INVOKABLE void initiateRequest();
 
private slots:
    void requestFinished(QNetworkReply* reply);
 
private:
    bb::cascades::ActivityIndicator *pleaseWaitActIndicator;
    bb::cascades::ListView *listViewProdotti;
    bb::cascades::ActionItem *actionDownload;
    bb::cascades::Label *labelAttenderePrego;
 
    QNetworkAccessManager *httpConnectionManager;
    QFile *productsFile;
};
 
#endif /* ProductList_H_ */
/**
* ProductList.cpp
* 
* Summary: scarica e visualizza un catalogo prodotti
* 
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
* 
* Date: 21.04.2013
* 
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
* 
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
#include "productList.h"
 
#include <QIODevice>
 
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/XmlDataModel>
#include <bb/cascades/Color>
 
#include <bps/bps.h>
#include <bps/locale.h>
 
using namespace bb::cascades;
 
productList::productList(bb::cascades::Application *app) : QObject(app)
{
    // Crea il QML document di root di avvio
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
    // espone la classe "this" per le chiamate da codice C++
    qml->setContextProperty("_appConfig", this);
    // Crea l'oggetto per la generazione della UI
    AbstractPane *root = qml->createRootObject<AbstractPane>();
 	// recupera gli oggetti dal file QML per la gestione all'interno del codice C++
 	// utilizzando il metodo "findChild"
    pleaseWaitActIndicator = root->findChild<ActivityIndicator*>("pleaseWaitIndicator");
    listViewProdotti = root->findChild<ListView*>("prodottiListView");
    labelAttenderePrego = root->findChild<Label*>("lblAttenderePrego");
    actionDownload = root->findChild<ActionItem*>("downloadFileAction");
 	// crea l'oggetto "httpConnectionManager" di tipo "QNetworkAccessManager" per 
 	// gestire la connessione
    httpConnectionManager = new QNetworkAccessManager(this);
 	// connette al metodo "requestFinished" all'evento "finished" che viene
 	// scatenato quando il download è stato completato
    bool result = connect(httpConnectionManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
    // crea il file nella cartella di default
    productsFile = new QFile("data/products_in_stock.xml");
 	// crea la UI e la attiva
    app->setScene(root);
}

void productList::initiateRequest()
{
    // attiva l'activityIndicator e visualizza la label con il messaggio di attesa
    pleaseWaitActIndicator->start();
    labelAttenderePrego->setVisible(true);
 	// assegna alla label il messaggio di attesa e imposta lo stile
    labelAttenderePrego->setText("Scarica la lista dei prodotti...");
    labelAttenderePrego->textStyle()->setColor(Color::DarkRed);
    // crea l'oggetto per gestire la richiesta http utilizzando la classe QNetworkRequest
    QNetworkRequest request = QNetworkRequest();
    // imposta l'URL della richiesta
    request.setUrl(QUrl("http://repository.mycompany.com/db/products/products_in_stock.xml"));
 	// esegue la richiesta http
    httpConnectionManager->get(request);
}

void productList::requestFinished(QNetworkReply* reply)
{
	// fermal'activityIndicator e lo nasconde
    pleaseWaitActIndicator->stop();
    labelAttenderePrego->setVisible(false);
 	// verifica se la richiesta non ha generato errori
    if (reply->error() == QNetworkReply::NoError)
    {
        // tenta di aprire il file "products_in_stock.xml" creato
        // nella cartella "data" per l'operazione di scrittura
        if (!productsFile->open(QIODevice::ReadWrite))
        {
        	// system dialog
            messageErrorFileOpen.show();
            return;
        }
 		// scrive il file con il contenuto scaricato
        productsFile->write(reply->readAll());
        productsFile->flush();
        productsFile->close();
        // crea un datamodel di tipo XML e assegna il file appena salvato come sorgente
        // del datamodel creato
        XmlDataModel *dataModel = new XmlDataModel();
        dataModel->setSource(QUrl("file://" + QDir::homePath() + "/products_in_stock.xml"));
		// assegna il nuovo dataModel alla lista
        listViewProdotti->setDataModel(dataModel);
    }
    else
    {
    	// ci sono errori, li visualizzo all'utente
        labelAttenderePrego->setText("Problemi con la connessione di rete!!!");
        labelAttenderePrego->textStyle()->setColor(Color::Red);
    }
    reply->deleteLater();
}

Chi desidera approfondire l’argomento, può trovare maggiori dettagli qui.

Nicola D’Amico
Italian Developer for BlackBerry® smartphones

Come utilizzare la LeadingVisual in QML per realizzare un PullToRefresh

Qualche settimana fa abbiamo visto come utilizzare la LeadingVisual in una ListView. In quel caso l’utilizzo che abbiamo fatto era abbastanza semplice, implementare un’area nascosta che conteneva dei campi di ricerca. Quello che vogliamo fare adesso è sfruttare questa proprietà della ListView per realizzare l’effetto PullToRefresh, simile a quello che troviamo ad esempio nell’applicazione Twitter.

Il PullToRefresh consiste in pratica in un movimento verso il basso della ListView che, superato un certo limite e rilasciato fa ad esempio scatenare l’aggiornamento dei dati di una lista.

Per realizzare questa funzionalità, abbiamo solo bisogno di estendere il componente ListView nativo, aggiungendovi la cattura del movimento Pull/Up della lista e la conseguente gestione dell’evento associandovi le funzionalità di aggiornamento della lista.

Come prima cosa, creiamo un nuovo componente che estenda il componente ListView. Lo chiamiamo per semplicità “CustomRefreshListView” e definiamo un Signal che ci servirà per comunicare con la pagina che conterrà questo componente e una serie di eventi per gestire la ListView.

ListView {
	...
	...
    signal refreshTriggered()
	...
	...
    leadingVisual: RefreshPullComponent {
        id: refreshPullComponent
        onRefreshTriggered: {
			...
			...
        }
    }
    onTouch: {
        ...
        ...
    }
    onLoadingChanged: {
    	...
    	...
    }
}

Per ottimizzare la gestione del codice, il Container associato alla proprietà leadingVisual lo trasformiamo in un componente cosi da inserire in esso tutte le logiche di gestione. Lo chiamiamo ad esempio “RefreshPullComponent”.

/**
* RefreshPullComponent.qml
* 
* Summary: Custom Control Container per gestire il Pull To Refresh
*/
import bb.cascades 1.0

Container {
	// -----------------------------------------------------------------------
	// readyForRefresh: indica se il controllo è pronto per il refresh
	// refreshing: indica se la lista è attualmente in aggiornamento
	// valueToStartRefresh: è il valore in pixel oltre il quale viene scatenato
	// 					    l'evento di refresh della lista
	// -----------------------------------------------------------------------
    signal refreshTriggered
    id: refreshPullContainer
    property bool readyForRefresh: false
    property bool refreshing: false
    property int valueToStartRefresh: 20
    horizontalAlignment: HorizontalAlignment.Fill
    layout: DockLayout {
    }
    Container {
        id: statusBox
        horizontalAlignment: HorizontalAlignment.Fill
        ImageView {
        	// icona che indica che sarà eseguito il refresh
            id: imageRefresh
            imageSource: "asset:///images/refresh.png"
            verticalAlignment: VerticalAlignment.Center
            horizontalAlignment: HorizontalAlignment.Center
        }
        Label {
            id: labelStatus
            text: qsTr("Tirare per aggiornare la lista")
            verticalAlignment: VerticalAlignment.Center
            textStyle.textAlign: TextAlign.Center
            horizontalAlignment: HorizontalAlignment.Fill
        }
    }
    // aggiungiamo un activityIndicator per gestire i tempi di attesa
    Container {
        horizontalAlignment: HorizontalAlignment.Fill
        ActivityIndicator {
            id: loadingIndicator
            preferredWidth: 70
            preferredHeight: 70
            verticalAlignment: VerticalAlignment.Center
            horizontalAlignment: HorizontalAlignment.Center
        }
    }
    Divider {}
    attachedObjects: [
        LayoutUpdateHandler {
        	// alla modifica del layout viene verificata l'azione da eseguire
            id: refreshPullComponent
            onLayoutFrameChanged: {
                if (refreshing) {
                    return;
                }
                readyForRefresh = false;
				// in funzione dello spostamento visualizza il messaggio che indica all'utente cosa fare
                if (layoutFrame.y >= 0) {
                    imageRefresh.rotationZ = layoutFrame.y*2;
                    if (layoutFrame.y >= valueToStartRefresh) {
                        if (! refreshing) {
                            readyForRefresh = true;
                        }
                        labelStatus.text = qsTr("Rilasciare per aggiornare")
                    }
                } else if (layoutFrame.y >= -100) {
                    statusBox.visible = true;
                    labelStatus.text = qsTr("Tirare per aggiornare la lista");
                    imageRefresh.rotationZ = 0;
                } else {
                    imageRefresh.rotationZ = 0;
                }
            }
        }
    ]
    function released() {
    	// notifica che l'utente ha chiesto di aggiornare i dati
        if (readyForRefresh) {
            readyForRefresh = false;
            refreshing = false;
            refreshTriggered();
        }
    }
    onRefreshingChanged: {
    	// gestione dell'evento refresh per la visualizzazione dell'indicatore di caricamento
        if (refreshing) {
        	// il controllo è in aggiornamento, visualizzo l'indicatore
            statusBox.visible = false;
            loadingIndicator.visible = true;
            loadingIndicator.running = true;
        } else {
        	// Il refresh è stato completato, nascondo l'indicatore
            loadingIndicator.running = false;
            loadingIndicator.visible = false;
            statusBox.visible = true;
            refreshPullContainer.setPreferredHeight(0);
        }
    }
    function onListViewTouch(event) {
        refreshPullContainer.resetPreferredHeight();
        if (event.touchType == TouchType.Up) {
        	// scatena l'evento che indica che l'utente ha rilasciato e attiva l'aggiornamento
            released();
        }
    }
}

La sezione LayoutUpdateHandler è quella delegata alla gestione dell’intercettazione del movimento Pull/Up della lista. Quando l’utente esegue un movimento verso il basso superando il valore della proprietà “valueToStartRefresh” e successivamente compie un movimento verso l’alto, viene scatenata la funzione “released()” che a sua volta attiva la notifica del segnale alla pagina che potrà quindi aggiornarsi. Quando la pagina completa l’aggiornamento, la modifica della proprietà “loading” del componente “CustomRefreshListView” farà chiudere il LeadingVisual.

Vediamo adesso il codice completo del nuovo componente:

/**
* CustomRefreshListView.qml
* 
* Summary: Custom Control ListView con evento PullToRefresh
* 
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
* 
* Date: 14.04.2013
* 
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
* 
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
import bb.cascades 1.0

ListView {
	// refreshTriggered: evento gestito sulla listview che viene scatenato
	// 					 quando l'utente esegue il pull della lista e attiva
	//					 la procedura di aggiornamento
    signal refreshTriggered()
    // loading: indica se la listview è in fase di caricamento dati
    property bool loading: false
    id: customRefreshListView
    // utilizza l'area leadingVisual per visualizzare il messaggio
    // che invita ad eseguire un pull per aggiornare la lista.
    // Per rendere tutto più leggibile, si è realizzato un componente separato
    // con tutta la logica implementativa per gestire il PullToRefresh.
    leadingVisualSnapThreshold: 2.0
    leadingVisual: RefreshPullComponent {
        id: refreshPullComponent
        onRefreshTriggered: {
        	// viene scatenato l'evento refresh e segnalato alla pagina QML
        	// nella pagina QML dove è inserito questo controllo ListView Custom occorre
        	// connettere l'evento con una funzione locale che si occupa di gestire ad 
        	// esempio l'aggiornamento dei dati visualizzati dalla listview.
            customRefreshListView.refreshTriggered();
        }
    }
    onTouch: {
        refreshPullComponent.onListViewTouch(event);
    }
    onLoadingChanged: {
    	// quando la proprietà loading del controllo cambia, viene notificata la
    	// modifica alla proprietà refresh del controllo "refreshPullComponent"
        refreshPullComponent.refreshing = customRefreshListView.loading;
        // se l'aggiornamento del controllo è completa, chiude il leadingVisual
        // e sposta in posizione Top la listview
        if(!refreshPullComponent.refreshing) {
            scrollToPosition(ScrollPosition.Beginning, ScrollAnimation.None);
        }
    }
}

Di seguito invece vediamo come utilizzarlo all’interno di una pagina che visualizza una lista la cui sorgente dati è un un RSS:

/**
* TestCustomRefreshListView.qml
* 
* Summary: Test controllo CustomRefreshListView
* 
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
* 
* Date: 14.04.2013
* 
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
* 
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
import bb.cascades 1.0
import bb.system 1.0
import "component"

Page {
    // Titolo della homePage
    titleBar: TitleBar {
        title: "Test CustomRefreshListView"
    }
    Container {
        layout: StackLayout {
            orientation: LayoutOrientation.TopToBottom
        }
        Label {
            text: "Tirare per eseguire il refresh"
        }
        Container {
            layout: StackLayout {
                orientation: LayoutOrientation.TopToBottom
            }
			// ListView custom
            CustomRefreshListView {
                id: argumentList
                dataModel: searchDataModel
                listItemComponents: [
                    ListItemComponent {
                        type: "header"
                        Header {
                            title: ListItemData
                        }
                    },
                    ListItemComponent {
                        type: "item"
                        StandardListItem {
                            title: ListItemData.name
                        }
                    }
                ]
                onTriggered: {
                    // recupera l'elemento selezionato
					...
					...
                }
            }
        }
    }
    attachedObjects: [
        GroupDataModel {
            id: searchDataModel
            sortingKeys: [ "name" ]
            grouping: ItemGrouping.ByFirstChar
        },
        DataSource {
            id: searchDataSource
            // come source usiamo un RSS di Google
            source: "http://news.google.com/news?topic=h&output=rss"
            query: "/rss/channel/item"
            type: DataSource.Xml
            onDataLoaded: {
                searchDataModel.insertList(data);
                // caricamento della ListView completata. Modifica lo stato in (loading == false)
                argumentList.loading = false;
            }
            onError: {
                messageErrorData.show();
            }
        }
    ]
    onCreationCompleted: {
		// connette il metodo che attiva l'aggiornamento della lista all'evento refreshTriggered
        argumentList.refreshTriggered.connect(onRequestRefresh);
    }
    function onRequestRefresh() {
    	// aggiorna lo stato della lista in (loading == true)
        argumentList.loading = true;
        searchDataModel.clear();
        searchDataSource.load();
    }
}

Per chi desidera approfondire l’argomento, può trovare maggiori dettagli qui.

Nicola D’Amico
Italian Developer for BlackBerry® smartphones

Come accedere alla cartella “data” da QML utilizzando QDeclarativePropertyMap

La cartella “data” in BlackBerry OS 10 è la cartella dove vengono memorizzari i dati privati della nostra applicazione e si ha un completo accesso sia in lettura sia in scrittura. A tutti gli effetti può essere considerata la directory “home” dell’applicazione.

Alla luce di questo, diventa importante poter accedere a questa cartella dalle pagine QML per recuperare dei file, delle immagini o altro. Generalmente, se la nostra applicazione è complessa, possiamo recuperare questa informazione realizzando un’apposita classe che espone come proprietà il percorso completo per raggiunge la cartella “data”. Se non vogliamo realizzare un’apposita classe, abbiamo un’alternativa: utilizzare la classe QDeclarativePropertyMap.

Supponiamo di voler accedere al file “HelpList.xml” che è memorizzato nella cartella “data”. Nel file .cpp che crea gli elementi “root” della nostra applicazione, dichiariamo una proprietà di tipo “QDeclarativePropertyMap” cui assegniamo ad esempio il nome “xmlHelpFilename”.

QDeclarativePropertyMap *xmlHelpFilename = new QDeclarativePropertyMap;

Successivamente inseriamo nella proprietà creata il percorso completo del file utilizzando il metodo homePath() della classe QDir.

xmlHelpFilename->insert("HelpListFilename", QVariant(QString("file:///%1/HelpList.xml").arg(QDir::homePath())));

Non resta che esporre la proprietà creata ai file QML utilizzando il metodo “setContextProperty()“.

qml->setContextProperty("xmlFilenames", xmlHelpFilename);

Di seguito il codice completo.

#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
#include <bb/data/DataSource>
#include <bb/cascades/Container>

using namespace bb::cascades;
using namespace bb::system;

ApplicationUI::ApplicationUI(bb::cascades::Application *app)
: QObject(app)
{
	// crea il documento QML
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
    // dichiara la proprierà xmlHelpFilename
	QDeclarativePropertyMap *xmlHelpFilename = new QDeclarativePropertyMap;
	xmlHelpFilename->insert("HelpListFilename", QVariant(QString("file:///%1/HelpList.xml").arg(QDir::homePath())));
	qml->setContextProperty("xmlFilenames", xmlHelpFilename);
	...
	...
    // create root object for the UI
    AbstractPane *root = qml->createRootObject<AbstractPane>();
    // set created root object as a scene
    app->setScene(root);
}

Ed ecco come utilizzare la proprietà nel nostro file QML.

/**
* HelpList.qml
* 
* Summary: Help dell'applicazione
* 
* Author:  Nicola D'Amico
* 		   Italian Developer for BlackBerry® smartphones
* 		   Monza (MB) - Italy
* 
* Date: 12.04.2013
* 
* Twitter: http://twitter.com/WhiteSharkIT
* Linkedin: http://it.linkedin.com/in/nicoladamico
* 
* Copyright © 2013-2018 Nicola D'Amico. All rights reserved.
*/
import bb.cascades 1.0

Page {
    id: root
    // titolo della lista
    titleBar: TitleBar {
        title: qsTr("Guida all'uso")
    }
    Container {
        ListView {
            dataModel: XmlDataModel {
                source: xmlFilenames.HelpListFilename
            }           
            id: helpMenu
            horizontalAlignment: HorizontalAlignment.Fill
            layout: StackListLayout {}
            listItemComponents: [
				// definizione degli item della lista
				...
				...
            ]
            onTriggered: {
                //TODO: Implementare visualizzazione dettaglio Help
            }
        }
    }
}

Per chi desidera approfondire l’argomento relativo al filesystem del nuovo BlackBerry OS 10, può trovare maggiori dettagli qui.

Nicola D’Amico
Italian Developer for BlackBerry® smartphones

Iscriviti

Ricevi al tuo indirizzo email tutti i nuovi post del sito.

%d bloggers like this: