Creating your own cards

A great way to get your app noticed is to export your app's cards, so other apps can import them. Creating cards is similar to creating a small, focused app. One of the key differences, however, is that unlike an app, a card can have multiple instances running at the same time. The BlackBerry 10 OS creates a new instance for each client app. In many cases, the BlackBerry 10 OS will pool your card (pooling is discussed a bit later). This section can help you understand how you can use cards to expose your app's functionality to other apps.

A card shares an execution context across multiple instances of itself. For example, all instances of a card run in the same workspace directory and use the same permissions. This feature of cards is important when you consider how you use your resources. If you read and write files, make sure you coordinate and access across different instances of your card. Any cards that your app exports are packaged in the same .bar file as the app.

Registering a card target

Because a card is used through an invocation request, the first step in exporting a card is to register an invocation target for it. Registering card targets is the same as registering application targets except for a minor difference. The <invoke-target-type> tag must specify one of the card styles supported by the BlackBerry 10 OS. The valid values for <invoke-target-type> are card.previewer, card.composer, and card.picker. Registering in this way informs the BlackBerry 10 OS that you are exporting a card and describes the style of the card, so the BlackBerry 10 OS can handle it appropriately.

Just like apps, cards are packaged and identified as invocation targets in the bar-descriptor.xml file. Here's an example that shows you how to declare a card:

<invoke-target id="com.acme.myapp">
  <invoke-target-type>card.previewer</invoke-target-type>
  <filter>
    <action>bb.action.VIEW</action>
    <action>bb.action.SHARE</action>
    <mime-type>image/png</mime-type>
    <mime-type>image/jpeg</mime-type>
    <property var="uris" value="file://"/>
  </filter>
</invoke-target>

Invocation requests for cards

Your cards are bundled in the same executable file as your app. When your app starts, you should check the app startup mode to determine whether the app was started as an invocation and whether the invocation is for the full app or one of the exported cards.

Not applicable

int main(int argc, char *argv[])
{
    ... // Other functions in main

    // An application may be invoked while it is running, 
    //so it should listen for invocations
    InvokeManager invokeManager;
    
    // 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 not
    // normal, and will cause your app to stop working!!
    bool connectResult;
    
    // Since the variable is not used in the app, this is added to avoid a 
    // compiler warning.
    Q_UNUSED(connectResult);
    
    connectResult = QObject::connect(invokeManager, 
    SIGNAL(invoked(const bb::system::InvokeRequest&),
    &myApp, SLOT(onInvoke(const bb::system::InvokeRequest&));    
    
    // This is only available in Debug builds.
    Q_ASSERT(connectResult);

    ... // Other functions in the main

    switch(invokeManager.startupMode()) {
    case ApplicationStartupMode::LaunchApplication:
    // If the application was launched from the home screen, it can initialize
        break;
    case ApplicationStartupMode::InvokeCard:
    // If the application is invoked, 
    //it must wait until it receives an invoked(..) signal
    // so that it can determine the UI that it needs to initialize
        break;
    default:
    // Who am I and how did I get here?
        break;
    }

    // Drop the application into the exec loop 
    //so that it can start receiving messages
    return app.exec();
}

After you determine that the startup is configured for a card invocation, you need to wait to receive the invoked() signal so that you can determine the card you want to start, by checking the target value in the invocation as follows:

void MyAppOrCard::invoked(const bb::system::InvokeRequest&  request)
{

    // Initiate the appropriate target based on the invoke.target-key
    if(request.target() == "com.acme.application") {
        // Full app is already running and should now handle this invoke
    } else if(request.target() == "com.acme.message.previewer") {
        // card should init as the specified target
    } else if(request.target() == "com.acme.message.composer") {
        // card should init as the specified target
    } else…
} else {
    // Error: Who am I and how did I get here? 
    // Did I declare my target names and check against the right strings?
}

Card targets receive invocation requests in the same way as app targets do. For more information, see Receiving invocation.

Requesting your card to be closed

After a card finishes its task, it can request to be closed. When this request is processed, the card is transitioned off the screen and its parent app is notified. While the card closes itself, it can also send a response message to the parent app. Here's how a card requests to be closed:

Not applicable

// Assemble response message
CardDoneMessage message;
message.setData(tr("Card: I am done. yay!"));
message.setDataType("text/plain");
message.setReason(tr("Success!"));

// Send message
invokeManager->sendCardDone(message);

Preparing your card for pooling

You can create your cards so they can be pooled when a user closes the card. Pooling helps to keep frequently used cards ready for reuse, which reduces loading and transition time when cards are stacked. For example, if the user is viewing one email after another, instead of exporting a new card for every email, the BlackBerry 10 OS may pool the existing email previewer card and use it again.

When you're creating a card, you must consider several things before you handle pooling events, such as which resources your cards should clean up or keep when they're pooled. First, consider adding support for purging the state of the card. While pooled cards are not terminated, they may be suspended to keep them from processing while pooled. When your card is pooled, it should release resources such as files or database connections until it is resumed again. When a card receives a cardPooled() signal it should clear its current state and listen for future invocations. In addition, cards must be written to support multiple instances which run simultaneously within the same sandbox.

Second, if an app with cards defines several card targets, then when it's retrieved from the pool, the card may be invoked to service any of the declared targets. In other words, if a .bar file bundles a composer, previewer, and picker under a single entry point, then when it's retrieved from the pool, the app or card may be invoked as any of these targets.

Third, cards generally operate in full screen mode. However, some apps may show the status bar and will require the card to be resized if it covers the status bar. Therefore, if you're using C++, apps with cards should always include a cardResizeRequested() signal.

If you're using C++, the BlackBerry 10 OS manages the pooling of cards. The pooling is also dependent on varying factors of the device state. In certain situations, a card may not be pooled. In such cases, the card receives the exit message that indicates to the card that it should terminate. For more information, see Application in the API reference.

Preparing your cards for resizing and rotation

In some cases, your cards might be rotated or resized when the device orientation changes.

Not applicable

Cards can be rotated and resized. Rotation occurs when a user rotates the BlackBerry device. When the device is rotated, a cardResize() signal is sent to the card and indicates the new dimensions of the card. How much a card is resized depends on whether the parent app requires the status bar to be shown, in which case the entire screen card is resized to allow the status bar to be displayed. Here's how a card handles the resizing request and resizes its orientation:

// Connect to the card resize request signal.

// 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 not
// normal, and will cause your app to stop working!!
bool connectResult;

// Since the variable is not used in the app, this is added to avoid a 
// compiler warning.
Q_UNUSED(connectResult);

connectResult = QObject::connect(invokeManager, 
SIGNAL(cardResize(const bb::system::CardResizeMessage&)), 
card, 
SLOT(cardResize(const bb::system::CardResizeMessage&)));

// This is only available in Debug builds.
Q_ASSERT(connectResult);  
...
  
void cardResizeRequested(const bb::system::CardResizeMessage& resizeMessage)
{
    // Resize card based on the following information
    // Width available to the card.
    resizeMessage.width();  
    // Height available to the card.         
    resizeMessage.height();
    // The edge of the device that points upwards.         
    resizeMessage.upEdge();  
    // Orientation of the device (portrait or landscape).      
    resizeMessage.orientation()  
    … 
    // Respond after resizing is done.
    invokeManager->cardResized(resizeMessage);
}

A card’s orientation is aligned with the orientation of the parent app. If the parent app's orientation is fixed, any child cards in the stack also have a fixed orientation.

A card’s orientation is aligned with the orientation of the parent app. If the parent app's orientation is fixed, any child cards in the stack also have a fixed orientation.

When you create cards, you must add support for both landscape and portrait orientation so that the card follows the orientation of your app.

Card security

Cards have their own identity and sandbox environment that is shared with all instances of the card. Like apps, cards are processed in their own context and with their own permissions. It is important to pay attention to the capabilities and the data that you want to expose using your card.

Last modified: 2014-09-30



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

comments powered by Disqus