Invoke Cards

November 8, 2012 — 1 Comment

The Invocation Framework allows you to develop a total new kind of apps. You can provide functionality to other apps or you can use functionality from other apps.

To understand what I’m talking about you should at first read the Cascades Documentation about App integration.

I will explain you how easy this can be done. You’ll find the code Open Source at Github:

For the target app (providing functionality) OpenDataSpace: https://github.com/blackberry/opendataspace-cascades

For the client app (using these functions) File Upload 2 ODS: https://github.com/OpenDataSpace/file-upload2ods

I will include some code-snippets in this post – to understand all what I’m doing there you should take a look at the sources I just mentioned.

The Target APP

OpenDataSpace is a full-blown-Application with a TabbedPane as Root and NavigationPanes + many Pages on top and also using an Application Menu.

The Application looks something like this:

To provide some functionality to other apps you can use “Cards”.

There are two different types of Cards:

  • Previewer: Like Pages pushed from NavigationPane (Sliding in from right, can be closed by peeking over a threshold)
  • Composer: Like Sheets (Sliding in from the bottom, cannot be closed from peek)

You don’t know the workflow of your clients and to support them best my tip: always provide both types: Previewer and Composer.

To invoke Cards using the Invocation Framework you have to describe them in the bar-descriptor.xml of your Target App like this:

<invoke-target id="io.ods.bb10.card.upload.previewer">
        <type>card.previewer</type>
        <invoke-target-name>Upload to ODS (Previewer)</invoke-target-name>
  		<icon>assets/images/upload-icon.png</icon>
        <filter>
            <action>bb.action.OPEN</action>
            <action>bb.action.SHARE</action>
      	    <mime-type>image/jpeg</mime-type>
      	    <mime-type>image/png</mime-type>
            ... your mime-types
        </filter>
    </invoke-target>

You need a unique invoke-target id, the type (card.previewer or card.composer), the action (in this case OPEN and SHARE) and one or more mime-types. I have defined some targets: a card.previewer, a card.composer and “APPLICATION”. Cards will be integrated in clients workflow – APPLICATION will open the target Application itself.

Inside your target app you can test from where your application was launched:

QString qmlDocument;
	switch (m_invokeManager->startupMode()) {
	case ApplicationStartupMode::LaunchApplication:
		// the normal Launch
		// our main QML document: the HomeScreen with a custom Background Image
		m_isLaunchedEmbedded = false;
		m_isCard = false;
		qmlDocument = "asset:///main.qml";
		qDebug() << "ApplicationStartupMode: LAUNCHED from homescreen";
		break;
	case ApplicationStartupMode::InvokeApplication:
		// Invocation. Someone Opened the App thru Invocation
		// our main QML document: the HomeScreen with a custom Background Image
		m_isLaunchedEmbedded = false;
		m_isCard = false;
		qmlDocument = "asset:///main.qml";
		qDebug() << "ApplicationStartupMode: LAUNCHED from Invocation";
		break;
	case ApplicationStartupMode::InvokeCard:
		// Card Opened by another App
		// the APP is now running embedded and invisible for the user
		// we only need a small part of the functionality,
		// so we use a different root object
		m_isLaunchedEmbedded = true;
		m_isCard = true;
		qmlDocument = "asset:///UploadCard.qml";
		qDebug() << "ApplicationStartupMode: LAUNCHED as CARD";
		break;
	default:
		// our main QML document: the HomeScreen with a custom Background Image
		m_isLaunchedEmbedded = false;
		m_isCard = false;
		qmlDocument = "asset:///main.qml";
		break;
	}

The most important decision: I’m using different documents as QML Root: main.qml for the full-blown app launched from Invocation or from Homescreen and Uploadcard.xml if launched as Card embedded into another app (the client-app).

Why am I doing this ? If launched embedded I only need a small part of the application:

My tip: only instantiate what you need ! Always think about performance and memory-footprint ! This is also important if you plan to get a certification “Built for BlackBerry 10

Using another root object as QML Document it looks much better:

If you’re providing more then one Card or you need different pages depending on the data you got from the client, then you can instantiate what you need lazy – but thats another story for a blog post.

Now I want to describe a sample workflow between two applications:

  • File Upload 2 ODS: the client app
  • OpenDataSpace: the target app

Both applications are available from BlackBerry AppWorld and the sources from Github – this should make it easy for you to follow.

The Client APP

File Upload 2 ODS is a simple App using Cascades FilePicker to browse files, select a file and the upload this file to the cloud. Instead of developing all the code by yourself you can use the UploadCard from OpenDataSpace.

The easiest way to get exactly what you need is to do a “bound invocation” – a bound invocation knows the target id where a unbound unvocation does a query and gets a list back for all targets providing something for a given mime-type.

To Invoke Card from the Client using a bound Invocation you can do it all from QML or you can do it from C++.

First step the user is doing: pick a file from FilePicker, then invoke the target app. (I will write another blog about the FilePicker, so we concentrate now on Invocation Framework)

This is the QML – way to embed a Card from target app:

InvokeActionItem {
            id: shareAction
            ActionBar.placement: ActionBarPlacement.OnBar
            query {
                // little trick: always use same mime type, because query needs a mime type
                // ODS then takes a look at file path suffix and ignores the mime-type
                mimeType: "image/png"
                invokeActionId: "bb.action.OPEN"
                invokeTargetId: "io.ods.bb10.card.upload.previewer"
            }
            onTriggered: {
                // next trick: all properties from query are read-only
                // but we can use the data, which is read-write to provide the URL
                data = picker.selectedFile
            }
        },

We’re using an InvokeActionItem and provide the Action (OPEN) and the target id.

Tip: We cannot do a bound invocation from InvokeActionItem out-of-the-box because a query is used and a query always needs a mime-type. The trick is to provide a valid mime-type of the target together with the target id – then only one result will be found. Another trick: all query-parameters are read-only, so we cannot use the URI to provide the file path – we’re using the data and fill the value in onTriggered {}
Here’s the C++ way to invoke the target. In QML we’re using a normal ActionItem and call the C++ function:

ActionItem {
            id: cloudActionPreviewer
            title: qsTr("Preview") + Retranslate.onLanguageChanged
            imageSource: "asset:///images/my.png"
            ActionBar.placement: ActionBarPlacement.OnBar
            onTriggered: {
                app.invokeBoundODSPreviewer(picker.selectedFile)
            }
        }

In C++ we only need some lines of code:

void FileUpload2ODS::invokeBoundODSPreviewer(QString data) {
	InvokeRequest cardRequest;
	cardRequest.setData(data.toLatin1());
	cardRequest.setTarget("io.ods.bb10.card.upload.previewer");
	m_invokeManager->invoke(cardRequest);
}

Now the request is sent to the Invocation Framework and the Card is invoked.

The target has to handle the invoke:

void OpenDataSpace::handleInvoke(const InvokeRequest& request) {
	m_invokationTarget = request.target();
	// Invoked as Application
	if (m_invokationTarget == "io.ods.bb10.invoke") {
		m_isCard = false;
	}
	// invoked as embedded Card (Previewer) from OPEN or SHARE
	else if (m_invokationTarget == "io.ods.bb10.card.upload.previewer") {
		m_isCard = true;
	}
	// invoked as embedded Card (Composer) from OPEN
	else if (m_invokationTarget == "io.ods.bb10.upload.composer") {
		m_isCard = true;
	}
	if (m_isCard) {
		AbstractPane *p = Application::instance()->scene();
		bool ok = false;
		if (request.uri().isEmpty()) {
			ok = p->setProperty("filePath", request.data());
		} else {
			ok = p->setProperty("filePath", request.uri());
		}
		if (ok) {
			// do some stuff
		}
	} else {
		// do what needed if Invoked, per ex. switch to a specific TAB
	}
}

Seemless Workflow

All Pages from the target are seamless integrated into the workflow :)
The User goes thru the Pages (provided by the Target App) and uploads the file to the cloud or he went back without uploading using the BACK Button or peeking over the threshold.

The Target App sends a ChildCardDone – signal and the client receives this in a slot. The signal includes a message with the reason and data, so you can check it easy and go on with the workflow or notify the user.

Here’s the Target APP Code if the Card is done:

void OpenDataSpace::cardDone() {
	CardDoneMessage message;
	message.setData(tr(":)"));
	message.setDataType("text/plain");
	message.setReason(tr("save"));
	m_invokeManager->sendCardDone(message);
}

There’s a similar code for cardCancelded.
The Client APP code receivong the ChildCardDone Signal in a Slot:

void FileUpload2ODS::childCardDone(const bb::system::CardDoneMessage &message) {
	// Card Child done with Success ?
	if (message.reason() == "save") {
		emit cardSuccess();
	} else {
		emit cardCanceled();
	}
	// now close the card !
	m_invokeManager->closeChildCard();
}

If you want to notify the UI (QML) you can send a Signal (cardSuccess / cardCanceled) where the Page is connected to.

You see: only less code is needed as glue code between the Invocation Framework and Target / Client APPs.

The best thing: the user doesn’t know that he used another app under the hood: the target app is invisible and the Card is embedded into the client app.

BTW: The Cascades FilePicker itself also uses the Invocation Framework: you’re getting a ChildCardDone Message with reason ‘save’ if the user selected a file and with reason ‘cancel’ if the user canceled. To make it easy to handle from the ChildCardDone I’m using the same reasons to let the client know what happened.

Some Screenshots

Here are some screenshots:

The Applications we’re using:

————

———

——

——

—–

—–

—–

—–

—–

—–

—–

—–

—–

—–

—–

—–

—-

The Video


(c) 2012 Creative Commons License 3.0 (BY-NC-SA) by ekkescorner

One response to Invoke Cards

  1. 
    Apekshith Ramesha December 26, 2012 at 03:21

    Hello Ekke,
    I tried to invoke a card as a previewer to show my pictures in the app. i could able to send the request and the sliding page with back button does come. But I do not know how to set the URI info for the request. I’m trying to make use of the images in the assets of the program. And please let me know if you can able to make a simple sample app where you call all types of core apps as cards. Thank You very much and Cascades is really fun to code!! #BB10Believe

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s