We know that the Ubuntu mobile platform is a bit like the iPhone platform and is a single-tasking operating system, although the system itself has multi-tasking capabilities. If the current application is pushed to the background, the application will be automatically suspended and not run by the system. At this point, if our application needs to wait for a message, such as wechat, we need to use the Push Notification mechanism provided by the Ubuntu platform to implement our multi-tasking. When the notification is received, we can directly click on the received notification, and the application will run again to the foreground.

\

For Push Notification, we have an article (Client) and an article (Server) on our developer website that detail its mechanics. I don’t want to go into too much here. Those who are interested can read the article carefully. Today, I’m going to analyze a concrete example to better understand how to implement this feature on Ubuntu phones.

\

\

\

As can be seen from the above figure, the whole system consists of two parts: client side and server side. On the Server side, it is divided into a PushSever (push.ubuntu.com) and an App Server. App Server is used to manage our users’ Nick names and tokens. Inside it, there is a database.

\

To test, developers must have an Ubuntu One account. We need to create this account in the account in the phone’s “System Settings”.

\

When a QML application is in use:

\

Import Ubuntu.PushNotifications 0.1 PushClient {id: PushClient component. onCompleted: { notificationsChanged.connect(messageList.handle_notifications) error.connect(messageList.handle_error) } appId: "com.ubuntu.developer.push.hello_hello" }Copy the code

\

When we use the API above, Push Server will send a token to our client. This token relies on the phone’s own parameters and the “appId” seen above. With this token, we can register with our application server and live on the application server side. When we need to send a message, we have to register something like a nickname. The nickname will be bound to the token of our mobile client. Each time the other nickname wanted us to send a message, the application server could query the database to get our token and further push the message to our client through the push server. If our client wants to send messages to other clients, the same logic applies.

\

Currently, there is no specific description of PushClient on our developer website. We can learn about this API using the methods in the article “How to Get a detailed API interface for QML Package”.

Push Server is used to Push information. It is located at push.ubuntu.com. It has only one endpoint:/notify. To send a push message to a user. The application server can send an HTTP POST to the Push server with “Content-Type: Application /json” to Push our message. Here is a boilerplate of content for a POST body:

\

{" appID ": "com.ubuntu.music_music", "expire_on": "2014-10-08T14:48:00.000Z", "token": "LeA4tRQG9hhEkuhngdouoA==", "clear_pending": true, "replace_tag": "tagname", "data": { "message": "foobar", "notification": { "card": { "summary": "yes", "body": "hello", "popup": true, "persist": true } "sound": "buzz.mp3", "tag": "foo", "vibrate": { "duration": 200, "pattern": (200, 100), "repeat": 2 } "emblem-counter": { "count": 12, "visible": true } } } }Copy the code

\

\

appid: ID of the application that will receive the notification, as described in the client side documentation.
expire_on: Expiration date/time for this message, in ISO8601 Extendend format
token: The token identifying the user+device to which the message is directed, as described in the client side documentation.
clear_pending: Discards all previous pending notifications. Usually in response to getting a “too-many-pending” error.
replace_tag: If there’s a pending notification with the same tag, delete it before queuing this new one.
data: A JSON object.

From the above information format, we can see that token is a very important piece of information. With it, we can send push messages to the terminals we need.

\

We can use our SDK to create a simple routine. Here is a brief introduction to our main file main.qml:

\

Import QtQuick 2.0 import Qt. LABS. 1.0 the import Settings Ubuntu.Com ponents import Ubuntu.Com ponents. 0.1 0.1 as ListItems ListItem import Ubuntu.PushNotifications 0.1 import "components" MainView {id: "mainView" // objectName for functional testing purposes (autopilot-qt5) objectName: "mainView" // Note! applicationName needs to match the "name" field of the click manifest applicationName: "com.ubuntu.developer.ralsina.hello" automaticOrientation: true useDeprecatedToolbar: false width: units.gu(100) height: units.gu(75) Settings { property alias nick: chatClient.nick property alias nickText: nickEdit.text property alias nickPlaceholder: nickEdit.placeholderText property alias nickEnabled: nickEdit.enabled } states: [ State { name: "no-push-token" when: (pushClient.token == "") PropertyChanges { target: nickEdit; readOnly: true} PropertyChanges { target: nickEdit; focus: true} PropertyChanges { target: messageEdit; enabled: false} PropertyChanges { target: loginButton; enabled: false} PropertyChanges { target: loginButton; text: "Login"} }, State { name: "push-token-not-registered" when: ((pushClient.token != "") && (chatClient.registered == false)) PropertyChanges { target: nickEdit; readOnly: false} PropertyChanges { target: nickEdit; text: ""} PropertyChanges { target: nickEdit; focus: true} PropertyChanges { target: messageEdit; enabled: false} PropertyChanges { target: loginButton; enabled: true} PropertyChanges { target: loginButton; text: "Login"} }, State { name: "registered" when: ((pushClient.token != "") && (chatClient.registered == true)) PropertyChanges { target: nickEdit; readOnly: true} PropertyChanges { target: nickEdit; text: "Your nick is " + chatClient.nick} PropertyChanges { target: messageEdit; focus: true} PropertyChanges { target: messageEdit; enabled: true} PropertyChanges { target: loginButton; enabled: true} PropertyChanges { target: loginButton; text: "Logout"} } ] state: "no-push-token" ChatClient { id: chatClient onError: {messageList.handle_error(msg)} token: { var i = { "from" : "", "to" : "", "message" : "Token: " + pushClient.token } if ( pushClient.token ) messagesModel.insert(0, i); console.log("token is changed!" ); return pushClient.token; } } PushClient { id: pushClient Component.onCompleted: { notificationsChanged.connect(messageList.handle_notifications) error.connect(messageList.handle_error) onTokenChanged:  { console.log("token: +" + pushClient.token ); console.log("foooooo") } } appId: "com.ubuntu.developer.ralsina.hello_hello" } TextField { id: nickEdit placeholderText: "Your nickname" inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhPreferLowercase anchors.left: parent.left anchors.right: loginButton.left anchors.top: parent.top anchors.leftMargin: units.gu(.5) anchors.rightMargin: units.gu(1) anchors.topMargin: units.gu(.5) onAccepted: { loginButton.clicked() } } Button { id: loginButton anchors.top: nickEdit.top anchors.right: parent.right anchors.rightMargin: units.gu(.5) onClicked: { if (chatClient.nick) { // logout chatClient.nick = "" } else { // login chatClient.nick = nickEdit.text } } } TextField { id: messageEdit inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhPreferLowercase anchors.right: parent.right anchors.left: parent.left anchors.top: nickEdit.bottom anchors.topMargin: units.gu(1) anchors.rightMargin: units.gu(.5) anchors.leftMargin: units.gu(.5) placeholderText: "Your message" onAccepted: { console.log("sending " + text) var idx = text.indexOf(":") var nick_to = text.substring(0, idx).trim() var msg = text.substring(idx+1, 9999).trim() var i = { "from" : chatClient.nick, "to" : nick_to, "message" : msg } var o = { enabled: annoyingSwitch.checked, persist: persistSwitch.checked, popup: popupSwitch.checked, sound: soundSwitch.checked, vibrate: vibrateSwitch.checked, counter: counterSlider.value } chatClient.sendMessage(i, o) i["type"] = "sent" messagesModel.insert(0, i) text = "" } } ListModel { id: messagesModel ListElement { from: "" to: "" type: "info" message: "Register by typing your nick and clicking Login." } ListElement { from: "" to: "" type: "info" message: "Send messages in the form \"destination: hello\"" } ListElement { from: "" to: "" type: "info" message: "Slide from the bottom to control notification behaviour." } } UbuntuShape { anchors.left: parent.left anchors.right: parent.right anchors.bottom: notificationSettings.bottom anchors.top: messageEdit.bottom anchors.topMargin: units.gu(1) ListView { id: messageList model: messagesModel anchors.fill: parent delegate: Rectangle { MouseArea { anchors.fill: parent onClicked: { if (from ! = "") { messageEdit.text = from + ": " messageEdit.focus = true } } } height: label.height + units.gu(2) width: parent.width Rectangle { color: { "info": "#B5EBB9", "received" : "#A2CFA5", "sent" : "#FFF9C8", "error" : "#FF4867"}[type] height: label.height + units.gu(1) anchors.fill: parent radius: 5 anchors.margins: units.gu(.5) Text { id: label text: "<b>" + ((type=="sent")? to:from) + ":</b>" + message wrapMode: Text.Wrap width: parent.width - units.gu(1) x: units.gu(.5) y: units.gu(.5) horizontalAlignment: (type=="sent")? Text.AlignRight:Text.AlignLeft } } } function handle_error(error) { messagesModel.insert(0, { "from" : "", "to" : "", "type" : "error", "message" : "<b>ERROR: " + error + "</b>"
                })
            }

            function handle_notifications(list) {
                list.forEach(function(notification) {
                    var item = JSON.parse(notification)
                    item["type"] = "received"
                    messagesModel.insert(0, item)
                })
            }
        }
    }

    Panel {
        id: notificationSettings
        anchors {
            left: parent.left
            right: parent.right
            bottom: parent.bottom
        }
        height: item1.height * 9
        UbuntuShape {
            anchors.fill: parent
            color: Theme.palette.normal.overlay
            Column {
                id: settingsColumn
                anchors.fill: parent
                ListItem.Header {
                    text: "<b>Notification Settings</b>"
                }
                ListItem.Standard {
                    id: item1
                    text: "Enable Notifications"
                    control: Switch {
                        id: annoyingSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Enable Popup"
                    enabled: annoyingSwitch.checked
                    control: Switch {
                        id: popupSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Persistent"
                    enabled: annoyingSwitch.checked
                    control: Switch {
                        id: persistSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Make Sound"
                    enabled: annoyingSwitch.checked
                    control: Switch {
                        id: soundSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Vibrate"
                    enabled: annoyingSwitch.checked
                    control: Switch {
                        id: vibrateSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Counter Value"
                    enabled: annoyingSwitch.checked
                    control: Slider {
                        id: counterSlider
                        value: 42
                    }
                }
                Button {
                    text: "Set Counter Via Plugin"
                    onClicked: { pushClient.count = counterSlider.value; }
                }
                Button {
                    text: "Clear Persistent Notifications"
                    onClicked: { pushClient.clearPersistent([]); }
                }
            }
        }
    }
}
Copy the code

\

Here, create a nickname input box and a login button above it. Next, we create a dialog box to input information. Next, we create a ListView to display status, notifications, or traffic.

\

\

\

The chatclient. QML file is defined as follows:

\

Import QtQuick 2.0 import Ubuntu.Components 0.1 Item {property string Nick Property String Token property bool registered: false signal error (string msg) onNickChanged: { if (nick) { console.log("Nick is changed!" ); register() } else { registered = false } } onTokenChanged: { console.log("Token is changed!" ); register() } function register() { console.log("registering ", nick, token); if (nick && token) { console.log("going to make a request!" ); var req = new XMLHttpRequest(); req.open("post", "http://direct.ralsina.me:8001/register", true); / / the req. Open (" post ", "http://127.0.0.1:8001/register", true); req.setRequestHeader("Content-type", "application/json"); req.onreadystatechange = function() { // Call a function when the state changes. if(req.readyState == 4) { if (req.status == 200) { console.log("response: " + JSON.stringify(req.responseText)); registered = true; } else { error(JSON.parse(req.responseText)["error"]); } } } console.log("content: " + JSON.stringify(JSON.stringify({"nick" : nick.toLowerCase(), "token": token }))); req.send(JSON.stringify({ "nick" : nick.toLowerCase(), "token": token })) } } /* options is of the form: { enabled: false, persist: false, popup: false, sound: "buzz.mp3", vibrate: false, counter: 5 } */ function sendMessage(message, options) { var to_nick = message["to"] var data = { "from_nick": nick.toLowerCase(), "from_token": token, "nick": to_nick.toLowerCase(), "data": { "message": message, "notification": {} } } if (options["enabled"]) { data["data"]["notification"] = { "card": { "summary": nick + " says:", "body": message["message"], "popup": options["popup"], "persist": options["persist"], "actions": ["appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version"] } } if (options["sound"]) { data["data"]["notification"]["sound"] = options["sound"] } if (options["vibrate"]) { data["data"]["notification"]["vibrate"] = { "duration": 200 } } if (options["counter"]) { data["data"]["notification"]["emblem-counter"] = { "count": Math.floor(options["counter"]), "visible": true } } } var req = new XMLHttpRequest(); req.open("post", "http://direct.ralsina.me:8001/message", true); req.setRequestHeader("Content-type", "application/json"); req.onreadystatechange = function() {//Call a function when the state changes. if(req.readyState == 4) { if (req.status == 200) { registered = true; } else { error(JSON.parse(req.responseText)["error"]); } } } req.send(JSON.stringify(data)) } }Copy the code

This is used to send registration information and send information to the application server. Here we use an established good application server at http://direct.ralsina.me:8001.

\

Here, we must create an Ubuntu One account on our phone or in our emulator, otherwise the application will not run successfully.

\

Running our phone and emulator at the same time, we can see the following screen:

   \

\

  \

\

The entire “Hello” source is at: github.com/liu-xiao-gu…

\

The entire server source at the address: github.com/liu-xiao-gu…

\

To be able to run the application server, we must install the component on the server and select our own port address (such as 8001), which can be found in config.js in the server code:

\

module.exports = config = {
    "name" : "pushAppServer"
    ,"app_id" : "appEx"
    ,"listen_port" : 8000
    ,"mongo_host" : "localhost"
    ,"mongo_port" : 27017
    ,"mongo_opts" : {}
    ,"push_url": "https://push.ubuntu.com"
    ,"retry_batch": 5
    ,"retry_secs" : 30
    ,"happy_retry_secs": 5
    ,"expire_mins": 120
    ,"no_inbox": true
    ,"play_notify_form": true
}
Copy the code

Then run:

$nodejs server.js
Copy the code

So the server is set up.

\

\

\

\