📜 ⬆️ ⬇️

Push notifications on Android in InterSystems Ensemble on the example of traffic police fines



In many mobile applications that allow you to recognize fines and pay them, it is possible to receive information about new fines. For this, it is convenient to use sending push notifications to client devices.

Our application for paying fines was no exception. The server part is implemented on the Ensemble platform, in which, from version 2015.1, built-in support for push-notifications appeared at the right time.

First, a little theory


Push notifications are one of the ways to disseminate information when data comes from a provider to a user based on the parameters set.
')
In general, for mobile devices, the notification process looks like this:



To notify users of mobile applications, we use notification delivery services from which devices receive data. And just so you can not send a notification. The user must be subscribed to a push notification channel or to notifications from a specific application.

The following entities are available for working with push notifications in Ensemble:

» EnsLib.PushNotifications.GCM.Operation is a business operation for sending push notifications to Google Cloud Messaging Services (GCM) server. The operation also allows you to send one message to the application on several devices at once.

» EnsLib.PushNotifications.APNS.Operation is a business operation that sends a notification to the Apple Push Notifications server. To send messages to each implemented application, you will need a separate SSL certificate.

» EnsLib.PushNotifications.IdentityManager - Ensemble business process. Allows you to send messages to the user without thinking about the number and types of its devices. In essence, Identity Manager contains a table that associates all its devices with a single user ID. The Identity Manager's business process receives messages from other product components and forwards them to the router, which in turn sends all GCM messages to the GCM operation and each APNS message to the APNS operation configured with the corresponding SSL certificate.

» EnsLib.PushNotifications.AppService - a business service that allows you to send push-messages generated outside the product. In fact, the message itself can be generated somewhere inside Ensemble regardless of the product, while the service allows you to send these messages from Ensemble. All of these classes are described in detail in the Ensemble " Configuring and Using Ensemble Push Notifications " documentation.

Now how we have implemented the notification process


In our case, messages are generated by a specially developed business process inside the product, so the service was not useful to us. Also at this stage, we only have an Android application, so we have not used the APNS operation either. In fact, we used the lowest level method of sending directly through the GCM operation. Later, when implementing the iOS version of the application, it will be convenient to implement work with notifications through the Identity Manager, so that you do not have to analyze the type and number of devices. But now we will tell more about GCM.

To send notifications, you need to implement a process within the product and connect the desired business transaction. At the moment we have two separate processes for sending Push-notifications, each with its own logic: notifications for new fines, notifications about the termination of the discount on the fine. About each type will tell a little more.

First, about the general data scheme and settings required for the operation of all notifications.


NotificationProtocol: HTTP
PushServer: http://android.googleapis.com/gcm/send

The operation settings in the end look like this:


We need to save the client ID, devices (identifiers and types), a list of documents (driver's licenses and vehicle registration certificates). All this information is obtained in requests from the client when subscribing to notifications. So, we need classes:

Client - for storing clients, App - for storing devices, Doc - for storing document data:

Class penalties.Data.Doc Extends %Persistent { ///  (  ) Property type As %String; ///  Property value As %String; } Class penalties.Data.App Extends %Persistent { ///  (GCM  APNS) Property Type As %String; ///  Property ID As %String(MAXLEN = 2048); } Class penalties.Data.Client Extends %Persistent { ///     Google Play Services,    Property Email As %String; ///   Property AppList As list Of penalties.Data.App; /// ,     Property Docs As list Of penalties.Data.Doc; } 

To send notifications about new fines, we need to understand which penalties to send to the client, and which he has already seen, when entering the application. To do this, we have a NotificationFlow class in which we note that the client has already received information about the fine.

 Class penalties.Data.NotificationFlow Extends %Persistent { ///  (   email) Property Client As %String; ///  Property Penalty As %String; ///   Property Sent As %Boolean; } 

For convenience of perception below, when mentioning classes, we omit the package names. According to the content of the classes, it is clear what the process for new fines will look like: we pass through the list of documents for each client, make a request for fines at the GIS GMP (State Information System on State and Municipal Payments), check the received fines for presence in NotificationFlow, if found - we delete from the list, as a result we form a list of fines, about which we need to notify the client, go over the list of client devices and send push notification to each of them.

Upper level:


where clientkey is a context property, the default value of which is the identifier of the first-ordered client of the subscription stored in the Client class.

The subprocess looks like this:


Let's look inside the foreach blocks:


After this foreach block we have a ready-made request EnsLib.PushNotifications.NotificationRequest, in which it remains to add the text of the message. This is done in the foreach block by Docs.


And a small piece of code that fills the request data:

 ClassMethod getPenaltyforNotify(client As penalties.Data.Client, penaltyResponse As penalties.Operations.Response.getPenaltiesResponse, notificationRequest As EnsLib.PushNotifications.NotificationRequest) { set json="",count=0 set key="" for { set value=penaltyResponse.penalties.GetNext(.key) quit:key="" set find=0 set res=##class(%ResultSet).%New("%DynamicQuery:SQL") set exec="SELECT * FROM penalties_Data.NotificationFlow WHERE (Penalty = ?) AND (Client = ?)" set status=res.Prepare(exec) set status=res.Execute(value.billNumber,client.Email) if $$$ISERR(status) do res.%Close() kill res continue while res.Next() { if res.Data("Sent") set find=1 } do res.%Close() kill res if find {do penaltyResponse.penalties.RemoveAt(key), penaltyResponse.penalties.GetPrevious(.key)} else { set count=count+1 do notificationRequest.Data.SetAt("single","pushType") for prop="billNumber","billDate","validUntil","amount","addInfo","driverLicence","regCert" { set json=$property(value,prop) set json=$tr(json,"""","") if json="" continue do notificationRequest.Data.SetAt(json,prop) } set json="" set notObj=##class(penalties.Data.NotificationFlow).%New() set notObj.Client=client.Email set notObj.Penalty=value.billNumber set notObj.Sent=1 do notObj.%Save() } } if count>1 { set keyn="" for { do notificationRequest.Data.GetNext(.keyn) quit:keyn="" do notificationRequest.Data.RemoveAt(keyn) } do notificationRequest.Data.SetAt("multiple","pushType") do notificationRequest.Data.SetAt(count,"penaltiesCount") } } 

The process on discounts for payment of fines is implemented a little differently. At the top level:


The selection of fines at a discount is performed by the following code:

 ClassMethod getSaleforNotify() { //      kill ^mtempArray set res=##class(%ResultSet).%New("%DynamicQuery:SQL") //        set exec="SELECT * FROM penalties_Data.Penalty WHERE status!=2 AND addInfo LIKE '%%'" set status=res.Prepare(exec) set status=res.Execute() if $$$ISERR(status) do res.%Close() kill res quit while res.Next() { set discDate=$piece(res.Data("addInfo")," 50%   : ",2) set discDate=$extract(discDate,1,10) set date=$zdh(discDate,3) set dayscount=date-$p($h,",") //   5,2,1  0  if '$lf($lb(5,2,1,0),dayscount) continue set doc=$s(res.Data("regCert")'="":"sts||"_Res.Data("regCert"),1:"vu||"_Res.Data("driverLicence")) set clRes=##class(%ResultSet).%New("%DynamicQuery:SQL") // ,    set clExec="SELECT * FROM penalties_Data.Client WHERE (Docs [ ?)" set clStatus=clRes.Prepare(clExec) set clStatus=clRes.Execute(doc) if $$$ISERR(clStatus) do clRes.%Close() kill clRes quit while clRes.Next() { //  ,      set ^mtempArray($job,clRes.Data("Email"),res.Data("billNumber"))=res.Data("billDate") } do clRes.Close() } do res.Close() } 

At the exit, we have a global breakdown of penalties for customers. Now you have to go over this global one and send each client its fine, after making sure that it has not been paid elsewhere:


We fall into a cycle of penalties:


Actually, the difference between the processes is as follows: in the first case, we necessarily go over all our clients, in the second we select only clients who have fines of a certain type; in the first case, for several penalties there is one notice with the total number (there are customers who manage to pick up a lot of fines during the day), in the second case there are separate discounts for each discount.
In the process of debugging, we encountered a small feature of our messages, due to which we had to override certain system methods. One of the parameters of our message, we pass the number of the fine, which in general form looks something like "12345678901234567890". System classes of the operation of sending notifications convert such strings into numbers, and the GCM service, unfortunately, having received such a large number is perplexed and returns a “Bad Request”.

Therefore, we redefined the system class of the operation, call the ConvertArrayToJSON method in it, inside which we call ..Quote with the second parameter equal to 0, that is, do not convert strings consisting only of numbers into numbers, but leave them as strings:

 Method ConvertArrayToJSON(ByRef pArray) As %String { #dim tOutput As %String = "" #dim tSubscript As %String = "" For { Set tSubscript = $ORDER(pArray(tSubscript)) Quit:tSubscript="" Set:tOutput'="" tOutput = tOutput _ "," Set tOutput = tOutput _ ..Quote(tSubscript) _ ": " If $GET(pArray(tSubscript))'="" { #dim tValue = pArray(tSubscript) If $LISTVALID(tValue) { #dim tIndex As %Integer // $LIST .. aka an array // NOTE: This only handles an array of scalar values Set tOutput = tOutput _ "[ " For tIndex = 1:1:$LISTLENGTH(tValue) { Set:tIndex>1 tOutput = tOutput _ ", " Set tOutput = tOutput _ ..Quote($LISTGET(tValue,tIndex),0) } Set tOutput = tOutput _ " ]" } Else { // Simple string Set tOutput = tOutput _ ..Quote(tValue,1) } } Else { // Child elements #dim tTemp Kill tTemp Merge tTemp = pArray(tSubscript) Set tOutput = tOutput _ ..ConvertArrayToJSON(.tTemp) } } Set tOutput = "{" _ tOutput _ "}" Quit tOutput } 

No other problems in the implementation process were found. Total, the main things that need to be done to send notifications:


Actually, everything. Due to the use of Ensemble ready-made components, the implementation of the process takes a couple of hours, including debugging and testing.

At the exit, we have satisfied customers who timely learn about new fines and recall discounts on time.


See how it works in Android and iOS applications.

Source: https://habr.com/ru/post/301470/


All Articles