Developing a push-enabled app

You can use the following code samples to learn how to develop your own push-enabled app. The code samples show you how to use the Push Service APIs to connect with the Push Initiator and receive push messages on a BlackBerry device.

If you're using the Push Service SDK as the server-side library, the SDK automatically specifies the type of content in the header of the push message.

In your app, you can send up to 8 KB of any type of content (images, text, or audio). The following samples can be used to create an app that accepts push messages with the following types of content and content-type HTTP headers:

  • Text: text/plain
  • HTML: text/html
  • XML: application/xml
  • Image: image/jpeg, image/gif, image/png

If you implement a Push Initiator that doesn't use the Push Service SDK, or your Push Initiator uses only the low-level APIs, you can still use these code samples for testing purposes.

If you are developing your app in QML or C++, you can learn more in the Push API Reference.

If you are developing your app in C, you can find more information in the Push Service Library API reference.

Prerequisites

You need the following before you start developing your push-enabled app:

  • The BlackBerry 10 Native SDK
  • A device or simulator running BlackBerry 10
  • The provider application ID and the PPG URL that you receive in the confirmation email from BlackBerry when you register to evaluate the Push Service
  • A Push Initiator to test your app

You need the application ID and URL to receive content in your app.

Your app needs the Push permission (_sys_use_consumer_push) to work with push messages. If you are using BlackBerry Enterprise Service 10 or later, you don't need this permission.

Setting up the Push Service

The following code sample creates a Push Service instance to initialize a push session and perform push-related operations. You should create one instance of the Push Service instance in your app.

Not applicable

// The initializePushService()function initializes one instance 
// of bb::network::PushService. The function also connects the 
// signals for the following events: create session, create
// channel, destroy channel, register to launch, unregister from 
// launch, SIM card change, push transport ready, and a lost 
// connection with the PNS agent. The initializePushService() 
// function is defined in the PushNotificationService object.
// To pass the PushService signals up to the main object of our
// headless app, the PushService signals are connected to
// the PushNotificationService signals.

void PushNotificationService::initializePushService(){
    const Configuration config = m_configurationService.configuration();

    // ...
    m_previousApplicationId = config.providerApplicationId();
    
    m_pushService = new PushService(config.providerApplicationId(), 
    INVOKE_TARGET_KEY_PUSH, this);

    // Connect the signals
    checkConnectResult(QObject::connect(m_pushService, 
      SIGNAL(createSessionCompleted(const 
      bb::network::PushStatus&)), this, 
      SIGNAL(createSessionCompleted(const 
      bb::network::PushStatus&))));

    checkConnectResult(QObject::connect(m_pushService, 
      SIGNAL(createChannelCompleted(const
      bb::network::PushStatus&, 
      const QString)), this,
      SIGNAL(createChannelCompleted(const 
      bb::network::PushStatus&, const QString))));

    checkConnectResult(QObject::connect(m_pushService, 
      SIGNAL(destroyChannelCompleted(const 
      bb::network::PushStatus&)), this, 
      SIGNAL(destroyChannelCompleted(const 
      bb::network::PushStatus&))));

    checkConnectResult(QObject::connect(m_pushService, 
      SIGNAL(registerToLaunchCompleted(const 
      bb::network::PushStatus&)), this, 
      SIGNAL(registerToLaunchCompleted(const 
      bb::network::PushStatus&))));

    checkConnectResult(QObject::connect(m_pushService, 
      SIGNAL(unregisterFromLaunchCompleted(const 
      bb::network::PushStatus&)), this, 
      SIGNAL(unregisterFromLaunchCompleted(const 
      bb::network::PushStatus&))));

    checkConnectResult(QObject::connect(m_pushService, 
      SIGNAL(simChanged()), this, 
      SIGNAL(simChanged())));

    checkConnectResult(QObject::connect(m_pushService, 
      SIGNAL(pushTransportReady
      (bb::network::PushCommand::Type)), this, 
      SIGNAL(pushTransportReady
      (bb::network::PushCommand::Type))));

    checkConnectResult(QObject::connect(m_pushService, 
      SIGNAL(connectionClosed()), this, 
      SLOT(onConnectionClosed())));
    
}

void checkConnectResult(bool connectResult){
    // If any Q_ASSERT statement(s) indicate that the slot failed to 
    // connect to the signal, make sure you know exactly why this has 
    // happened. 

    // This is only available in Debug builds.
    Q_ASSERT(connectResult);
}

void PushNotificationService::createSession()
{
    initializePushService();
 
    m_pushService->createSession();
}

To invoke the APIs from the Push Service library, create a push_service_t structure in your main class.

push_service_t *ps = NULL;
int rc = push_service_initialize(&ps);
if (rc == PUSH_FAILURE || (ps == NULL)){
    printf("start_push_service: failed init push_service errno[%d] 
       error[%s]\n", errno, strerror(errno));

    return EXIT_FAILURE;
}

Creating a Push Notification Service (PNS) session

To create the session, you need to pass in the provider application ID and the invoke target key. You received the provider application ID when you registered with BlackBerry to use the Push Service. The invoke target key is a value that you generate. The value identifies your push-enabled app as the target of push content, and the app that's invoked when the content arrives.

Not applicable

Coming soon

rc = push_service_set_provider_application_id(ps, provider_app_id);
if (rc == PUSH_FAILURE) {
	printf("push_service_set_provider_application_id: errno[%d] 
	error[%s]\n", errno, strerror(errno));
	return false;
}
rc = push_service_set_target_key(ps, target_key);
if (rc == PUSH_FAILURE) {
	printf("push_service_set_target_key: errno[%d] error[%s]\n", 
	errno, strerror(errno));
	return false;
}

rc = push_service_create_session(ps, on_create_session_complete);
if (rc == PUSH_FAILURE) {
	printf("push_service_create_session: errno[%d] error[%s]\n",
	errno, strerror(errno));
	return false;
}

// The status_code parameter of on_create_session_complete()
// contains the result of the create session call. If there 
// are no errors, the result code is 0. If there is an error,
// status_code contains the result code.

void on_create_session_complete(push_service_t* ps, int status_code)

Creating a push channel

The following code sample creates a push channel to the PPG to receive push messages. Your app needs to create a channel the first time it starts, or if a destroy channel occurred and you want to start receiving push messages again.

Not applicable

// Call createChannel() from the 
// PushNotificationService object to create a channel. If the
// channel creation is successful, the createChannelCompleted()
// signal returns a token that represents the address of the
// device. The Push Initiator uses the token to send push 
// messages to the device. If the channel creation is 
// unsuccessful, display an error message.

void PushNotificationService::createChannel(){
      m_pushService->createChannel(m_configurationService.configuration().
      ppgUrl());   
}

// If the channel creation is successful,
// call subscribeToPushInitiator() from the
// PushNotificationService object to subscribe with the
// Push Initiator and pass the call to the 
// RegisterService object.

void PushNotificationService::subscribeToPushInitiator(
    const User& user, const QString &token){
      m_registerService.subscribeToPushInitiator(user,token);
}

// Handle the subscription with the Push 
// Initiator using the RegisterService object

void RegisterService::subscribeToPushInitiator(const User& user,
    const QString& token){
      // Keep track of the current user's information so it can
      // be stored later 
      m_currentUser = user;

      const Configuration config = m_configurationService.configuration();
    
      QUrl url(config.pushInitiatorUrl() + "/subscribe");
      url.addQueryItem("appid",config.providerApplicationId());
      url.addQueryItem("address",token);
      url.addQueryItem("osversion",deviceVersion());
      url.addQueryItem("model",deviceModel());
      url.addQueryItem("username",user.userId());
      url.addQueryItem("password",user.password());
      if (config.usingPublicPushProxyGateway()) {
        url.addQueryItem("type","public");
      } else { 
        url.addQueryItem("type","bds");
    }

    m_reply = m_accessManager.get(QNetworkRequest(url));

    // Connect to the reply finished signal
    
    checkConnectResult(connect(m_reply, SIGNAL(finished()),
      this,SLOT(httpFinished())));
}
// Create a channel to the PPG using push_service_create_channel()
// so that your app and the device it runs on can receive
// content whenever the PPG sends the content.

void on_create_channel_complete(push_service_t* ps, int status_code) {
    printf("on_create_channel_complete called statusCode[%d]", 
    status_code);
}

void create_channel_on_push_transport_ready(push_service_t* ps, 
int status_code) {
    push_service_create_channel(ps, 
    on_create_channel_complete, 
    create_channel_on_push_transport_ready);
}
// Send the request to create a channel through the PNS agent.
// You need the PPG URL to invoke the call to create a channel.
// You received the PPG URL when you registered with BlackBerry
// to use the Push Service.

rc = push_service_set_ppg_url(ps, ppg_url);
if (rc == PUSH_SUCCESS) {
    rc = push_service_create_channel(ps, 
    on_create_channel_complete,
    create_channel_on_push_transport_ready);
}
else{
// Handle the situation where the push_service_create_channel()
// request fails with a PUSH_ERR_TRANSPORT_FAILURE (10103)
// or PUSH_ERR_PPG_SERVER_ERROR (10110) status code.

    printf("push_service_set_ppg_url: errno[%d] error[%s]", errno,
    strerror(errno));
}

Receiving a push message

To receive a push message, you need to listen for a navigator invoke event in your main event loop. The action value of this event must be PUSH_INVOCATION_ACTION. If these criteria are met, then you can extract the invocation data into a push_payload_t structure using push_payload_create() and push_payload_set_payload().

Not applicable

// The onInvoked() function first checks the action property
// of InvokeRequest. If the property matches the string constant
// BB_PUSH_INVOCATION_ACTION, the function checks to see that
// the bb::network::PushService instance is created, and that the
// application ID is stored. If both are true, the headless
// app extracts the push message from the invoke request
// by passing the invoke request into the PushPayload constructor.
// The pushNotificationHandler() function then processes the push
// message.

void AppHeadless::onInvoked(const InvokeRequest& request){
    if (request.action().compare(BB_PUSH_INVOCATION_ACTION) == 0) {
      if (m_configurationService.hasConfiguration()) {
        // The underlying PushService instance might not have been
        // initialized when an invoke first comes in.
        // Initialize it here if it hasn't been
        // already. It requires an application ID (for consumer 
        // apps) so we have to check that configuration 
        // settings have already been stored.
        m_pushNotificationService->initializePushService();

        qDebug() << "Received push action";

       // Extract the incoming push message from 
       // the invoke request and process it
    
       PushPayload payload(request);
       if (payload.isValid()) {
         pushNotificationHandler(payload);
       }
      }
    }   
    else if (request.action().compare(BB_PUSH_COLLECTOR_COMMAND_ACTION) == 0){
      qDebug() << "Received command action" ;
      QVariantList commandData = m_jsonDA->
      loadFromBuffer(request.data()).toList();
      commandMessageHandler(commandData);
	}
}
void handle_navigator_event(bps_event_t *event) {
     int event_type = bps_event_get_code(event);
     printf("received event type [%d]\n", event_type);
 
     switch (event_type) {
       case NAVIGATOR_EXIT:
           printf("NAVIGATOR Exit event\n");
           shutdown = true;
           break;
       case NAVIGATOR_INVOKE_TARGET: {
           // Our handler was invoked.
           const navigator_invoke_invocation_t *invoke = 
             navigator_invoke_event_get_invocation(event);
           if(invoke) {

             const char *action = 
               navigator_invoke_invocation_get_action(invoke);
             printf("Got invoke: action='%s'\n", action ? action : 
                    "NULL");

             if (strcmp(action,PUSH_INVOCATION_ACTION) == 0){

               // Get the jsonData from the invoke object and put 
               // it into a push_payload_t structure.
               const unsigned char* raw_invoke_data = 
                 (unsigned char*)
                 navigator_invoke_invocation_get_data(invoke);
               int invoke_data_len =
                 navigator_invoke_invocation_get_data_
                   length(invoke);

               printf("Creating push_payload_t from 
                 raw_invoke_data\n");
               push_payload_t* push_payload;
               if(push_payload_create(&push_payload) == 
                  PUSH_FAILURE){
                  printf("failed to create push_payload. 
                   errno[%d]\n", errno);
                  return FAILURE;
                }

                if (push_payload_set_payload(push_payload,
                       raw_invoke_data, invoke_data_len) == 
                       PUSH_SUCCESS &&
                       push_payload_is_valid(push_payload)){
                     printf("push_payload_t is valid\n");
                     process_push_payload(push_payload);
                 } else {
                     printf("push_payload_t is NOT valid\n");
                 }

                 // Cleanup
                 push_payload_destroy(push_payload);
                }
             }
             break;
         }
         default:
            break;
    }
 }

// When the push message is in the push_payload_t structure,
// here's how you can retrieve the data from the structure,
// and loop through all of the headers and their values.

void process_push_payload(const push_payload_t* payload)
{
     const unsigned char* data = push_payload_get_data(payload);
     size_t data_length = push_payload_get_data_length(payload);

       printf("data length : [%d]\n", data_length);

       size_t headers_length = 
         push_payload_get_headers_length(payload);
       int i;
 
       for(i=0; i <headers_length;i++){

         const push_header_t* header = 
           push_payload_get_header(payload,i);

         if (header){
             const char* header_name = 
               push_header_get_name(header);
             const char* header_value = 
               push_header_get_value(header);

             if (header_name){
                 printf("Header name: [%s]\n", header_name);
             }

             if (header_value){
                 printf("Header value: [%s]\n", header_value);
             }
        }
    }
}

Acknowledging the receipt of a push message

The Push Initiator can send a confirmed push message with app-level reliability that requires an acknowledgement from the push-enabled app.

Not applicable

// The isAckRequired property is checked when it calls the
// pushNotificationHandler()function. The app acknowledges
// the receipt of the push message by accepting it using the
// acceptPush() function.

// If an acknowledgement of the push is required (that is,
// the push was sent as a confirmed push, which is equivalent
// terminology to the push being sent with app level
// reliability)then you must either accept or reject the push.

if (pushPayload.isAckRequired()) {
	// In our sample, we always accept the push, but situations 
	// might arise where an app might want to reject
	// the push. For example, after looking at the headers that
	// came with the push or the data of the push, we might
	// decide that the push received did not match what we
	// expected, so we might want to reject it.

	m_pushNotificationService->acceptPush(pushPayload.id());
 
if (push_payload_set_payload(push_payload,
	  raw_invoke_data, invoke_data_len) == 
	  PUSH_SUCCESS &&
	  push_payload_is_valid(push_payload)){
	    printf("push_payload_t is valid\n");
	    process_push_payload(push_payload);
	  } else {
	    printf("push_payload_t is NOT valid\n");
	  }

// Cleanup
push_payload_destroy(push_payload);

Stopping the receipt of push messages

The following code sample destroys the push channel to the PPG to stop receiving push messages.

Not applicable

// Call destroyChannel() from the
// PushNotificationService object to destroy a channel

void PushNotificationService::destroyChannel(){
	m_pushService->destroyChannel();   
}

// If the destroy channel is successful,
// call unsubscribeFromPushInitiator() from the 
// PushNotificationService object to unsubscribe from the Push
// Initiator, and pass the call to the UnregisterService
// object

void PushNotificationService::unsubscribeFromPushInitiator(const 
    User &user){
	  m_unregisterService.unsubscribeFromPushInitiator(user);
}

// Handle the unsubscribe operation from the 
// Push Initiator using the UnregisterService

void UnregisterService::unsubscribeFromPushInitiator(const 
    User& user){
	  // Keep track of the current user's information.
	 

	  m_currentUser = user;
 
	  const Configuration config = m_configurationService.configuration();
	  QUrl url(config.pushInitiatorUrl() + "/unsubscribe");
	  url.addQueryItem("appid",config.providerApplicationId());
	  url.addQueryItem("username",user.userId());
	  url.addQueryItem("password",user.password());
     
	  m_reply = m_accessManager.get(QNetworkRequest(url));
 
	  // Connect to the reply finished signal.
    
	  checkConnectResult(connect(m_reply, SIGNAL(finished()), this, 
	    SLOT(httpFinished())));
}
// When you shut down your app, you should explicitly
// deallocate any memory that was allocated for the
// push_service_t structure using push_service_cleanup().
// You also need to remove the Push Service file descriptor
// from the list monitored by BPS using bps_remove_fd().

rc = push_service_cleanup(ps);
if (rc == PUSH_FAILURE) {
	printf("push_service_cleanup: errno[%d] error[%s]\n", errno, 
	strerror(errno));
}

if (bps_remove_fd(pushPpsFd) == BPS_FAILURE){
	printf("Failed to remove PPS file descriptor\n");
}

Detecting a changed SIM card

When a user changes the SIM card on a device, the PNS agent automatically destroys the channel and discards the push message for security reasons. A new user might be using the device, and that user shouldn't see or receive push messages that were intended for the previous user. You should implement a callback function to handle the SIM change.

Not applicable

// Implement the onSimChanged() function in your headless
// app  and connect a slot to the simChanged() signal

AppHeadless::AppHeadless(){
	// ...
	checkConnectResult(QObject::connect(m_pushNotificationService, 
	  SIGNAL(simChanged()), this, SLOT(onSimChanged())));
	// ...
}

void AppHeadless::onSimChanged(){
	// Remove the currently registered user 
	// (if there is one) and unsubscribe the
	// user from the Push Initiator because
	// switching SIMs might indicate that you  
	// are dealing with a different user 
    // Also, remove all pushes and push
	// history from the database.
	m_pushNotificationService->handleSimChange(m_pushHandler);

	sendStatusToUI(PUSH_COLLECTOR_SIM_CHANGE);
}
push_service_set_sim_change_callback(on_sim_change);
// to create a new channel to the PPG.

void on_sim_change(push_service_t* ps) {
	push_service_create_channel(ps, on_create_channel_complete, 
	create_channel_on_push_transport_ready);
}

Handling a push transport error or PPG server error

There might be occasions when your app can't create or destroy a channel because of a push transport error or a PPG server error. A push transport error occurs when there's a problem with the mobile network, the user's wireless connection, or the low-level communications layer that the Push Service uses. A PPG server error occurs when the PPG server is unavailable and returns an error. These errors don't occur if you're using the Push Service with the BlackBerry Enterprise Service 10 or later.

Not applicable

// Channel operations fails. Push Service issues an error 10103
// (Transport Error) or error 10110 (PPG server error). Error is
// displayed.

void AppHeadless::onCreateChannelCompleted(const 
    bb::network::PushStatus &status, const QString &token){
      // ...

      // Typically in your own app you don't
      // display this error to your users
      QString message = tr("Create channel failed with error code: 
        %0").arg(status.code());

      Configuration config = m_configurationService.configuration();

      switch(status.code()){
        case  PushErrorCode::NoError:

          // ...
        break;

        case  PushErrorCode::TransportFailure:
          message = tr("Create channel failed as the push transport is 
          unavailable. "
          "Verify your mobile network and/or Wi-Fi are turned 
          on. "
          "If they are on, you will be notified when the push 
          transport is available again.");
        break;

        case PushErrorCode::SubscriptionContentNotAvailable:
          message = tr("Create channel failed as the PPG is currently 
          returning a server error. "
          "You will be notified when the PPG is available 
          again.");
        break;

      }

    sendStatusToUI(PUSH_COLLECTOR_CREATE_CHANNEL_COMPLETE, message, 
      status.code());
}

// Connect a slot to the pushTransportReady() 
// signal so that the app can create or destroy the channel

// The pushTransportReady() signal returns a 
// bb::networkPushCommand object that contains 
// the last Push Service API call that failed 
// (either CreateChannel or DestroyChannel)

AppHeadless::AppHeadless(){
    // ...
    checkConnectResult(QObject::connect(m_pushNotificationService, 
      SIGNAL(pushTransportReady(bb::network::PushCommand::Type)),
      this, SLOT(onPushTransportReady(bb::network::PushCommand::Type))));
    // ...
}

void AppHeadless::onPushTransportReady(bb::network::PushCommand::Type 
  command){
      if (command == PushCommand::CreateChannel){
        m_pushNotificationService->createChannel();
      } else {
        m_pushNotificationService->destroyChannel();
      }
}

Not applicable

Handling a lost connection to the PNS agent

There might be rare occasions when your app loses its connection to the PNS agent running on the device. The PNS agent maintains a connection between the PPG and the app, and forwards push notifications from the PPG to the appropriate app instance.

When the app loses the connection, it can't perform these operations: create a session, create or destroy a channel, register to start in the background when a push message arrives, or unregister from starting in the background. When these operations fail because of the lost connection, the Push Service issues a ConnectionClosed error code.

Not applicable

// Connect a slot to the connectionClosed() signal
// to handle this error

checkConnectResult(QObject::connect(m_pushService, 
	SIGNAL(connectionClosed()),
	this, SLOT(onConnectionClosed())));

// When the connection is lost, notify the 
// user and try to re-establish the connection to
// the PNS agent

void PushNotificationService::onConnectionClosed(){
	if (!m_timer->isActive()){
	  emit noPushServiceConnection();
	  m_timer->start(m_retryTime);
	  return;
	}
		    
	// Try to reconnect to the Push Agent
	if (!m_pushService->reconnect()){
		
	  // Reconnect was unsuccessful
	  // Check to see if the max wait time has been reached
	  // If it has, stop and notify the user that  
	  // reconnecting to the PNS Agent was unsuccessful
	  // Otherwise, double the amount of time to wait before
	  // retrying, and then try again

	  if (m_retryTime >= MAX_WAIT_TIME){
	    qWarning() << "Max number of retry attempts reached. Could 
	    not re-establish connection to Push Agent.";
	    emit pushAgentConnectionStatusChanged(false);
	    m_timer->stop();
	  } else {
	    qDebug() << "No connection to Push Agent";
	    m_retryTime = m_retryTime * 2;
	    m_timer->setInterval(m_retryTime);
	  }
	} else {
	  // Reconnect was successful.
	  // Notify the user and call createSession.
      qDebug() << "Connection to Push Agent re-established!";
	  m_timer->stop();
	  m_retryTime = INITIAL_WAIT_TIME_MILLISECONDS;
	  emit pushAgentConnectionStatusChanged(true);
	  createSession();
	}

}

// If the connection to the PNS agent is closed, the Push Service
// issues a PUSH_ERR_CONNECTION_CLOSE (501) error. Your 
// app needs to re-establish the connection with the PNS
// agent by calling push_service_get_fd. Periodically until the
// function returns a valid file descriptor. You should implement
// a callback function to handle this situation.

push_service_set_connection_close_callback(ps, 
	on_connection_closed);

// The on_connection_closed() function starts a backoff timer that
// runs periodically and calls push_service_get_fd() to get a
// valid file descriptor. The interval of time between each call
// to push_service_get_fd() gets longer with each call.

void on_connection_closed(push_service_t* ps) {
	// Start backoff timer that periodically calls 
	// push_service_get_fd() until push_service_get_fd() 
	// returns a valid file descriptor.
}

Last modified: 2015-05-07



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus