App Tethering : Sharing Persistent Resources Part 1

In the last post, we went through one method of discovery, AutoConnect, and then built a couple of apps that used AutoConnect to find, authenticate and pair with each other.

That’s fine, but there’s not a lot of point in having two apps connected to each other unless they can interact. In App Tethering, that interaction comes in two forms: sharing resources and sharing actions. In this article we’re going to start looking at sharing resources.

Resources in App Tethering are simply pieces of data you wish to share with remote apps. The key things to understand about Resources are:

  • All resources have a type, either:
    • Data – strings, booleans and numbers (eg. Integer, Single, Double)
    • Streams – everything else. Could be an image, could be a dataset, could be a file, whatever. If you can get it into a TStream somehow, you can share it.
  • All resources have a lifetime:
    • Persistent – a resource you create at design-time, rather like creating a persistent TField in a database app. The implications are a little different though, the biggest being that a remote app can subscribe to a persistent resource and be notified whenever it changes. You don’t know if zero, one or 100 apps are subscribed to your resource, you simply update the value locally and move on.
    • Transient – a transient resource is one you send from code. Rather than a resource that remote apps can subscribe to, you send a transient resource to a specific remote app. If you want to send it to three remote apps, you send it three times.
  • All resources are one way
    • This one took me a little while to realise, but let’s say you have a persistent resource in App1, and App2 is subscribed to it. If the value changes in App1, App2 will be notified and receive the new value. However, if App2 changes that value, it does not flow back to App1. The resource is one way. (We’ll look at how to deal with this shortly).

To be honest, that’s probably enough to get started, so let’s update our two apps from last post to share a resource.

Open up your App1 and App2 projects from last time, and bring up both main forms. As a first step, we’re going to share a string resource from App1 to App2. Bring up App1’s main form and select the TTetheringAppProfile component. You’ll notice it has a Resources property, so bring up the property editor for that and create a new resource. Set its name to SomeText. Also note its ResType property is set to Data (not Stream), its IsPublic property is set to True (meaning it will be available to remote apps) and its Kind property is set to Shared.

The last one, Kind, might need some explaining. Remember I said that resources were one way: there is a sender and one or more receivers. Setting a resource’s Kind property to Shared means you’re the sender. When we get to App2 we’ll see the receiver side, but let’s finish off App1 first.

Congrats, you just created a persistent resource. However, we need some way to get a value into this resource, so go back to the form in App1 and drop down a TEdit. Right-click on it and select the Add Item | TEditButton menu. This will add a button on the right end of the edit. Select that button and set its Text property to Update. Then select the TEdit and set it’s TextPrompt to SomeText. Your form should look like this:

Double-click on the Update button and put the following code in the OnClick event.

For Delphi:

procedure TForm3.EditButton1Click(Sender: TObject);
begin
 TetheringAppProfile1.Resources.FindByName('SomeText').Value := Edit1.Text;
end;

For C++:

void __fastcall TForm1::EditButton1Click(TObject *Sender)
{
  TetheringAppProfile1->Resources->FindByName("SomeText")->Value = 
    TResourceValue::_op_Implicit(Edit1->Text);
}

If we ran App1 now, typed some text into the edit box and clicked Update, our persistent resource would be updated and any subscribed remote apps would be notified. However, right now we have no remote apps subscribed, so let’s fix that.

Bring up the main form of App2. Drop down another Label and then go to the TetheringAppProfile component and add a new resource to its Resources property. Make sure its name is the same as the one we just added to App1, that is, SomeText. Leave the ResType set to Data and IsPublic set to True, but change the Kind property to Mirror. This will cause a subscription to be setup between this local resource and the remote shared resource of the same name in App1. Anytime the value changes in App1, we’ll be notified.

How will we be notified? The Resource has an OnResourceReceived event where we can put code that will run when we are notified of a change. Note, don’t confuse this with the OnResourceReceived event of the TetheringAppProfile component. We’ll cover that one later. What we’re talking about here is the OnResourceReceived event of the persistent resource we just created.

Add an event handler for the resource’s OnResourceReceived event with the following code.

For Delphi:

procedure TForm4.TetheringAppProfile1Resources0ResourceReceived(
const Sender: TObject; const AResource: TRemoteResource);
begin
  Label2.Text := AResource.Value.AsString;
end;

For C++:

void __fastcall TForm2::TetheringAppProfile1Resources0ResourceReceived(TObject * const Sender,
TRemoteResource * const AResource)
{
  Label2->Text = AResource->Value.AsString;
}

AResource is a parameter passed into our event handler containing the new value of the resource and we are extracting its value as a string and putting it into the Text property of the label we dropped on the form.

Now we should be able to run both App1 and App2, click the connect button, then type some text into the edit box and click Share and have it appear in the label in App2. Change the text in the edit again and click Share and it should be updated again. I’ll give you a minute to have a little play.

While in this demo I’m simply displaying the resources in the UI, that doesn’t mean you have to. Maybe you are receiving the updated resource and inserting it into a database. Or posting it via REST to salesforce. Doesn’t matter. Once you have it in code you can do whatever you want with it.

Close all your apps down, then run App1 again and start a few copies of App2. Click Connect in App1 and it should find all the App2’s running. Note the code in App1 that displays the ID of App2 doesn’t handle the case of multiple App2’s, but you should see all the App2’s showing the Identifier of App1 in their label. Now share some text from App1 and you should see all your App2’s receive it. For the little amount of work we’ve actually done building these apps, I think that’s pretty cool.

Before we wrap up for this post, let’s look at the flow of events and network traffic for this example. It’s pretty simple, but humour me.

App 1
Network
App 2
Resources.FindByName(‘SomeText’).Value := Edit1.Text
UDP Point to PointResource Updated (UDP Point to Point) Resource[0].OnResourceReceived

I told you it was simple, but a few things I want to call your attention to that will matter later on:

  • The notification is still going out on the network via UDP Point-to-Point.
  • The new value is actually carried in the notification message itself (there is no further chatter between them as App2 requests the value, etc)
  • App2 doesn’t send back any acknowledgement. It’s basically send and forget.

Here’s a network dump of the actual network packet:

This is from an earlier run when the text I sent was “Hello Peers”, but you can see on the right hand side that it is a TetheringNotification, it contains the Identifier of the TetheringManager. It then has the constant NOT_UPDRES, short for NOTification of UPDated RESource, followed by the Identifier of the resource that was updated. Lastly, it has some metadata about the resource (ResourceType, DataType) and finally the new value of the resource itself, in this case “Hello Peers”.

Why is this important? Well, next post we’ll continue on the path of Persistent Resources, looking at Streams, and the network behaviour for them looks very different indeed.

You can get the source for the sample apps at this stage of the series, in both C++ and Delphi, but to really understand what is going on there is no substitute for building the apps yourself. They aren’t that big.

10 Comments

  • I followed along with the last post and everything worked great – the two apps connected. For this one I’m not getting the values to update – I’ve checked everything a few times and it all seems the match what you described. The update event is definitely firing, but on the App2 I never get the Resources0Received event.. Any ideas?

  • Found the problem, I had been testing out using a password and only left it one place..

    I played around with this a bit more and I find it weird that if App1 has a password App2 can connect without one.. However, if I put a password in that is wrong for App2 it can’t connect, so it has to be blank or the correct password.

    When App1 has a password and App2 is blank they say they can connect, but then the data doesn’t sync (which is what I was running into originally).

    Is this buggy behavior or am I missing how this is supposed to be working?

    • Hi Michael,

      The first part makes sense to me. In our case App1 is initiating the connection (it calls AutoConnect), and App2 is receiving the connection request and then requesting a password first. App2 will only do this if it has a value in its Password property. So if App2 has no password property, it determines that no password is required, regardless of what App1 is expecting.

      I know I said to ignore the concepts of Client and Server, but let’s break that rule for a second. You try to connect to a website. It’s the website (the one receiving the connection request) that determines whether a password is required or not.

      > When App1 has a password and App2 is blank they say they can connect, but then the data doesn’t sync

      The second part is weird. I can reproduce it here and I suspect it is a bug. I’ve logged it internally so I’ll let you know when I find out.

      Cheers
      Malcolm

  • […] In the next post, we’ll do something with this connection, specifically sharing Resources between … […]

  • […] Last post we were introduced to sharing resources, in particular, persistent resources, and we wrote an example to share a persistent resource of type Data between two apps. […]

  • May I ask why the maximum length of a Data string is limited to +/- 8100 char. When the length is more then +/- 8100, then the OnResourceReceived no longer works.

    • hmmm, AFAIK that’s not a limit of App Tethering. I’ve just tested it with strings twice that length with no issue. However, UDP does have a packet size limit, one which varies depending on the network environment. I wonder if that could be it?

      I assume that length works for you as a Stream resource?

  • Yes it does work when using it as a Stream resource. Thank you Malcolm for the UDP packet size hint. I’ll check this.

  • […] Now that we know how to work with Persistent Resources, Transient Resources are going to be a walk in the park. […]

  • […] After the last few posts, you should be getting comfortable sharing data between multiple apps. That leaves us the last of our original four conceptual areas to cover off : Actions. […]

Join the Discussion

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>