Infinite LiveCode – An Example

by Mark Waddingham on May 16, 2016 4 comments

Prologue

The engineering team at LiveCode recently published a technical whitepaper on how we’d like to make LiveCode Builder interoperate better with other languages by improving its so-called ‘foreign function interface’.

In this post, I’d like to work through an example of how Infinite LiveCode might be used to write an extension for an ability the engine does not have: battery status monitoring on iOS.

There are several steps involved in working out how to build an extension which hooks into foreign APIs:

  1. Define what you want to be able to do
  2. Find the foreign APIs that you need to use and figure out how they work
  3. Work out a suitable set of functionality to expose to LiveCode Script
  4. Wrap the foreign APIs for use from LiveCode Builder
  5. Write a LiveCode Builder module which uses the wrapped foreign APIs to build the LiveCode Script functionality.

I’ll go through each of these five steps in turn.

Step 1 – Define what you want to do

This is perhaps the most subjective of the steps involved here, and probably one of the most difficult! As my Mathematics PhD supervisor used to continually remind me, “Don’t even attempt to answer a question until you actually understand it!”

In general, the need for something usually arises from a specific use-case so it is usually best to start with a limited scope, get something working and then try and widen it, rather than trying to do all that could possible be done in the first instance.

This is particularly relevant when foreign APIs are involved. In particular, system APIs tend to be quite low-level to ensure they don’t assume any specific method of use or application. This means you need to be quite clear about the actual high-level features you want to achieve.

Of course, in this case, what I want to do is quite clear: my goal is to allow LiveCode Script to access the status and charging level of the battery on iOS – ideally with notifications of when these things change so I don’t have to continually poll the status.

Step 2 – Find and understand the foreign APIs

When you get to this step you are probably in one of two situations: you either have a library you have in mind that you want to use to provide features, or you want to do something which you know your target platform has support for and you need to find out how it does it. You may have seen an app which seems to use such support on the given platform. Your question is, how does it do it, and how can I get LiveCode to do this too!

In the first situation, a good place to start is the homepage for the library involved. Typically, it will contain documentation, mailing lists, faqs, sample code, and it is probably the best place to start mining for information.

We are in the second situation. I know that battery monitoring on iOS exists, I have seen it in other apps. Let’s see if Google can give us information on how it is done. Indeed, my very first search “ios battery status api” gave all the fruit I needed in the top 3 results:

  1. Sample code from Apple on how to do it: https://developer.apple.com/library/ios/samplecode/BatteryStatus/Introduction/Intro.html
  2. The reference information for the relevant Objective-C class: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIDevice_Class/
  3. A question on StackOverflow:
  4. http://stackoverflow.com/questions/11807295/how-to-get-real-time-battery-level-on-ios-with-a-1-granularity

Here, resource 1 is great for seeing how the actual system APIs are used in practice, resource 2 gives the gory details of how the features work and resource 3 has useful information about what you actually get by using the features found in 2.

Of course, after finding this information, you need to actually understand it! This is generally a matter of experience, and don’t be surprised if you have to widen your search slightly to get to grips with the particular platform’s nuances and general approach to things. This is something which tends to become easier over time as you become more experienced with dealing with a particular platform’s APIs. You don’t need to necessarily understand how Objective-C works, for example, just how Apple uses Objective-C to provide the features to developers.

The features we need to use are based around some pretty standard patterns on iOS: patterns which you find for most iOS features which monitor physical state.

Looking at the UIDevice class reference we can see that there is a section of properties entirely centred around battery status monitoring in the section ‘Getting the Device Battery State’. The properties concerned are:

  • batteryLevel – the battery charge level for the device (read-only) expressed as a number between 0.0 (fully discharged) and 1.0 (fully charged), or -1.0 if battery monitoring isn’t available.
  • batteryMonitoringEnabled – A boolean value (true or false) indicating whether battery monitoring is enabled for the app.
  • batteryState – the battery state for the device (read-only) expressed as one of Unknown, Unplugged, Charging, Full (this uses the UIDeviceBatteryState enum – which is defined later on in the reference). Here, ‘Unknown’ (like -1.0 for batteryLevel) means that battery monitoring isn’t enabled.

As well as these three properties, you can see from the batteryMonitoringEnabled property that there are also two related ‘notifications’. A notification on iOS is a global message which the system can send, and which you can subscribe to (explicitly) to hear about it. These two notifications are:

  • UIDeviceBatteryLevelDidChangeNotification – tells you when the batteryLevel property changes. e.g. From half full to full
  • UIDeviceBatteryStateDidChangeNotification – tells you when the batteryState property changes. e.g. From charging to not charging

With this information you can start to see how this works. In order to find out the current battery charge level (via batteryLevel) and the current battery charge state (batteryState) you first need to enable battery monitoring (batteryMonitoringEnabled). When turned off, you will get ‘-1.0’ from batteryLevel, ‘Unknown’ from batteryState, and no notifications. When turned on, you’ll get meaningful values from batteryLevel and batteryState whilst also getting the notifications when one of them changes.

Step 3 – Work out the LiveCode Script functionality

Okay so now we’ve got a handle on what we want to do, and how iOS will let us do it, the next piece of the puzzle is figuring out how to expose the functionality to LiveCode Script! Or putting it another way, how do we tell LiveCode what we want it to do?

There are a few things to remember about LiveCode Script when considering this:

  • LiveCode Script is a ‘scalar+array’ language; this means that the best functionality revolves around using either strings/numbers or arrays as values.
  • LiveCode Script cannot express pointers; in particular, ‘object-like’ things you might want to manipulate in LiveCode Script have to be named, and accessed via name.
  • LiveCode Script allows both event-driven coding and poll-driven coding; the best functionality lets people use either.
  • LiveCode Script is ‘English-like’; the best functionality has naming which reads in an ‘English-like’ fashion.
  • LiveCode Script has many existing syntax patterns; the best functionality works in a similar way to an existing feature which has the same structure.

Taking what you learnt in the previous step we can see that this feature requires starting and stopping the battery monitoring, and whilst the monitoring is active we can query the level and state properties and will also receive change notifications from them. This is a similar pattern to a number of other mobile-related features, and in particular the sensor (location, heading etc.) features.

Taking the sensor feature as an existing pattern to build upon, the following seems like a perfectly reasonable set of syntax:

command startMonitoringBattery
Enable the device’s battery monitoring service. Whilst the service is enabled, the batteryLevel() and batteryState() functions can be used to find out information about the battery. Additionally, the batteryLevelChanged and batteryStateChanged messages will be sent to the current card of the default stack when the values change.

command stopMonitoringBattery
Disable the device’s battery monitoring service. Whilst the service is disabled, the batteryLevel() and batteryState() functions will return empty, and no batteryLevelChanged or batteryStateChanged messages will be sent.

function batteryMonitoringIsEnabled
Returns true if the battery monitoring service is currently enabled, and false otherwise.

function batteryMonitoringIsDisabled
Returns true if the battery monitoring service is currently disabled, and false otherwise.

function batteryLevel
If the battery monitoring service has been enabled (by calling startMonitoringBattery) then this function will return the charge level of the device’s battery. The charge level is returned as a number between 0.0 and 1.0 with 0.0 meaning completely discharged, and 1.0 meaning completed charged. If the battery monitoring service is not currently enabled, then the function will return empty.

function batteryState
If the battery monitoring service has been enabled (by calling startMonitoringBattery) then this function will return the charging state of the device’s battery. The charging state is returned as one of the following strings:

  • “unplugged”: the device is not plugged into power; the battery is discharging
  • “charging”: the device is plugged into power and the battery is less than 100% charged
  • “full”: the device is plugged into power and the battery is 100% charged
    If the battery monitoring service is not currently enabled, then the function will return empty.

message batteryLevelChanged
While the battery monitoring service is enabled, the batteryLevelChanged message will be sent to the current card of the defaultStack whenever the battery level changes.

message batteryStateChanged
While the battery monitoring service is enabled, the batteryStateChanged message will be sent to the current card of the defaultStack whenever the battery state changes.

This set of syntax can be provided to LiveCode Script from Builder by implementing a Builder module with the following interface:

library module com.livecode.system.battery
	public handler startMonitoringBattery() returns nothing
	public handler stopMonitoringBattery() returns nothing
	public handler batteryMonitoringIsEnabled() returns Boolean
	public handler batteryMonitoringIsDisabled() returns Boolean
	public handler batteryLevel() returns optional Number
	public handler batteryState() returns String
end module

We’ll see how this module might be implemented in Step 5, after we’ve looked at how to wrap the foreign functions we need.

Step 4 – Wrap the foreign APIs

With all the research and groundwork done, we can now start on the actual coding side of things. Enter Infinite LiveCode.

In order for us to be able to use LiveCode Builder easily to call into the system APIs we need to use, we first need to write a specification for the parts of the UIDevice class that we need to access. This specification will be used by the tool we propose to develop (if Infinite LiveCode funds) to generate Builder code which allows us to easily call into the system features.

In this case, we have 5 methods on the UIDevice class to wrap, 2 notifications and we also need to define constants for the return value of GetBatteryState so we can avoid using numeric constants to check its return value.

First the methods. We need to describe how we want each of them to be called from Builder. Each definition consists of three parts:

  • How we want to call the method from Builder (the ‘outer’ signature)
  • How you call the method from its native language (the ‘inner’ signature)
  • How parameters and return value map between the two

A possible description of the methods we need for this example are as follows:

handler UIDeviceGetCurrentDevice() returns Pointer
	wrap objc UIDevice +(void *)currentDevice
	return result
end handler

handler UIDeviceGetBatteryLevel(in pDevice as Pointer) returns Number
	wrap objc UIDevice -(float)batteryLevel
	return result 
end handler

handler UIDeviceGetBatteryState(in pDevice as Pointer) returns Integer
	wrap objc UIDevice -(int)batteryState
	return result
end handler

handler UIDeviceGetBatteryMonitoringEnabled(in pDevice as Pointer) returns Boolean
	wrap objc UIDevice -(BOOL)batteryMonitoringEnabled
	return result
end handler

handler UIDeviceSetBatteryMonitoringEnabled(in pDevice as Pointer, in pEnabled as Boolean) 
returns nothing
	wrap objc UIDevice -(void)setBatteryMonitoringEnabled:(BOOL)enable
	in pEnabled into enable
	return nothing
end handler

I’ll not go into the details of these too much, apart from mentioning the fact that the lack of object types in Builder does not stop us being able to interoperate with object-oriented languages (like Java and Objective-C) as we simply use the observation that a method call like:

Foo tFoo
	tFoo.Bar(100)

Can be rewritten as:

Foo tFoo
	FooBar(tFoo, 100)

I.e. a method call on an object is equivalent to a command which takes the object as a first parameter.

Next we need to consider the notifications. On iOS, a notification is a globally named entity which individual Objective-C objects can connect to using the NSNotificationCenter object. There will be a library function for doing the connection from Builder doing the heavy lifting for you, however the names for them still need to be imported from the foreign code:

notification UIDeviceBatteryLevelDidChangeNotification
notification UIDeviceBatteryStateDidChangeNotification

Finally, we need to define some constants related to the return value of GetBatteryState. The Objective-C method here returns a ‘enum’, which in Objective-C is just a collection of integer constants:

constant UIDeviceBatteryStateUnknown is 0
constant UIDeviceBatteryStateUnplugged is 1
constant UIDeviceBatteryStateCharging is 2
constant UIDeviceBatteryStateFull is 3

This proposed specification would generate a Builder module with the following interface:

module com.livecode.platform.ios.uidevice
public handler UIDeviceGetCurrentDevice() returns Pointer
public handler UIDeviceGetBatteryLevel(in pDevice as Pointer) returns optional Number
public handler UIDeviceGetBatteryState(in pDevice as Pointer) returns optional Integer
public handler UIDeviceGetBatteryMonitoringEnabled(in pDevice as Pointer) returns Boolean
public handler UIDeviceSetBatteryMonitoringEnabled(in pDevice as Pointer, 
in pEnabled as Boolean) 
returns nothing

public constant UIDeviceBatteryStateUnknown is 0
public constant UIDeviceBatteryStateUnplugged is 1
public constant UIDeviceBatteryStateCharging is 2
public constant UIDeviceBatteryStateFull is 3

public variable UIDeviceBatteryLevelDidChangeNotification as Pointer
public variable UIDeviceBatteryStateDidChangeNotification as Pointer
end module

With this, we now have the pieces we need to implement the library module described in step 3.

Step 5 – Write the library module

Builder (currently) has three kinds of module which you can write:

  • module: a collection of constants, handlers, variables and types which can be used from other Builder modules.
  • widget module: a description of a LiveCode widget which can be created and manipulated from LiveCode (using LiveCode Script).
  • library module: a collection of Builder handlers which can be used from LiveCode Script, just like an external.

As we are wanting to add a collection of handlers that can be called from LiveCode Script, we need a library module in this case.

Using the foreign functions

Before we go ahead and try and implement the higher-level syntax we want to use from LiveCode Script, we need to figure out how to call the low-level functions.

We’ve wrapped the methods of the UIDevice class in iOS. To call one of them we need to have a instance of a UIDevice object to call the methods on.

Looking again at the reference for the class, we see that calling the ‘currentDevice’ method (which we’ve wrapped as UIDeviceGetCurrentDevice) returns a ‘singleton’ object instance representing the device the application is running on. When you see an Objective-C method which is described as returning a singleton it simply means that you will get the same object every time you call it. Furthermore, the returned object will be ‘autoreleased’ which means that you don’t have to concern yourself with memory management details – as long as you don’t try to store the pointer longer than the handler you are using it in. Putting it simply, calling “UIDeviceGetCurrentDevice” will get us an object to work with.

So the pattern for using the UIDevice methods we have wrapped is along the following lines:

variable tDevice as Pointer
	put UIDeviceCurrentDevice() into tDevice
	put UIDevice(tDevice, …) into …

We’ll first take a look at implementing the state querying functions, then (finally) look at the implementation of starting and stopping the battery monitoring service.

State querying implementation

We have three functions to implement here: one to query whether battery monitoring is enabled, one for the battery state and one for the battery level.

To return the current state of battery monitoring we need to use the UIDeviceGetBatteryMonitoringEnabled() handler which wraps the foreign function:

public handler isBatteryMonitoringEnabled() returns Boolean
		return UIDeviceGetBatteryMonitoringEnabled(UIDeviceGetCurrentDevice())
	end handler

Notice here that we don’t actually bother storing the UIDevice pointer in a variable temporarily, as we only need to use it once!

The next handler to implement is the converse of isBatteryMonitoringEnabled(): isBatteryMonitoringDisabled(). These are merely logical negations of each other, so the best approach is to implement the latter in terms of the former:

public handler isBatteryMonitoringDisabled() returns Boolean
		return not isBatteryMonitoringEnabled()
	end handler

This leaves us with the two battery related query functions – batteryState and batteryLevel. For the batteryState function we need to map the integer return value of the foreign method to our chosen string:

public handler batteryState() returns String
		variable tState as Integer
		put UIDeviceGetBatteryState(UIDeviceGetCurrentDevice()) into tState
		if tState is UIDeviceBatteryStateUnplugged then
			return “unplugged”
		else if tState is UIDeviceBatteryStateCharging then
			return “charging”
		else if tState is UIDeviceBatteryStateCharged then
			return “charged”
		end if
		return “”
	end handler

For the batteryLevel function, the only thing we need to note is that if battery monitoring is not enabled then it will return -1.0, and we want this to map to ‘nothing’ (which maps to the empty string in LiveCode Script):

public handler batteryLevel() returns optional Number
		variable tLevel as Number
		put UIDeviceGetBatteryLevel(UIDeviceGetCurrentDevice()) into tLevel
		if tLevel is -1.0 then
			return nothing
		end if
		return tLevel
	end handler

Battery monitoring service implementation

We now need to implement the library functions which start and stop the device’s battery monitoring service. Turning on and off the battery monitoring service is done by making calls to the UIDeviceSetBatteryMonitoringEnabled() foreign function, but we also need to register to receive the notifications so we can send the proposed messages when they trigger.

This registration involves using a Builder library function to ask the system to invoke Builder handlers (as a callback) whenever one of the notifications occur.

The callback functions themselves need to get a reference to the LiveCode Script object which is the current card of the default stack, and then post a message to the object:

private handler handleBatteryStateChanged(in pInfo as Pointer)
		variable tTargetObject as ScriptObject
		resolve script object “this card” into tTargetObject
		post “batteryStateChanged” to tTargetObject
	end handler

	private handler handleBatteryLevelChanged(in pInfo as Pointer)
		variable tTargetObject as ScriptObject
		resolve script object “this card” into tTargetObject
		post “batteryLevelChanged” to tTargetObject
	end handler

To manage the attachment of these callbacks to the relevant iOS notifications, we’ll use notional Builder library handlers (these don’t actually exist yet but will be created as part of the Infinite LiveCode project):

ObjCRegisterNotification(in pNotification as Pointer, in pCallback as Handler, out rCookie as Pointer)
	ObjCDeregisterNotification(in pNotificationCookie as Pointer)

The register handler here will cause the passed handler to be invoked whenever the specified notification occurs. The function returns a ‘cookie’ which can then be used by the deregister handler.

This allows us to implement the startMonitoringBattery and stopMonitoringBattery handlers as follows:

	variable mBatteryStateCookie as optional Pointer
	variable mBatteryLevelCookie as optional Pointer
	public handler startMonitoringBattery()
		if mBatteryStateCookie is not nothing then
			return
		end if
		ObjCRegisterNotification(UIDeviceBatteryStateChanged, 
handleBatteryStateChanged, 
mBatteryStateCookie)
		ObjCRegisterNotification(UIDeviceBatteryLevelChanged, 
handleBatteryLevelChanged, 
mBatteryLevelCookie)
		UIDeviceSetBatteryMonitoringEnabled(UIDeviceGetCurrentDevice(), true)
	end handler

	public handler stopMonitoringBattery()
		if mBatteryLevelCookie is nothing then
			return
		end if
		UIDeviceSetBatteryMonitoringEnabled(UIDeviceGetCurrentDevice(), false)
		ObjCDeregisterNotification(mBatteryStateCookie)
		put nothing into mBatteryStateCookie
		ObjCDeregisterNotification(mBatteryLevelCookie)
		put nothing into mBatteryLevelCookie
	end handler

Here we use the mBatteryLevelCookie variable as a guard to ensure we don’t try to start the service if it is already running, and to not try and stop the service if it hasn’t been started.

Epilogue

In this post we’ve shown how it would be possible to use the features proposed by Infinite LiveCode to provide a simple wrapper around the battery monitoring service on iOS.

Whilst complete (as per the original goal), this example could be expanded in a number of ways to make it more useful. For example:

  • Make the feature cross-platform; in particular, add an Android implementation
  • Allow the feature to be used from other Builder modules (e.g. so you could use it to write a battery status widget).
  • Allow per-object subscription to the battery notifications

This has been a fairly involved article in which we have covered a number of areas that may be new to you. It may look a little daunting on first reading. However it’s important to bear in mind that:

  • Once the work to wrap this API has been done, it does not need to be done again – battery monitoring will become readily available to all
  • When you have learned how to create your first API wrapper the next one is going to get much easier
  • You do not need to leave LiveCode (other than optionally to install a colorizing LCB editor like the freely available Atom). There are no third party build tools to install. There are no compilers to configure
  • You can start off very small, looking up the OS API guides, wrapping a single function and trying it out in LiveCode then gradually adding to it. This makes it easy to tinker with, experiment and play around with APIs of all sorts
  • A very similar process can be used to wrap DLLs and many other libraries including thousands of freely available open source libraries
  • These sorts of wrapper widgets are easy to share in the community
  • Once there are lots of examples out there on how to do this, it will get easier. Wrapping a new API like this can probably be done within a couple of hours when you’ve done a few
  • We are building an entire set of fully working and documented examples in the Infinite LiveCode project
  • Once you have done the work for one platform (in this case iOS) you can reuse at least some of the knowledge to go cross platform. To prove this point, we have included an Appendix showing how this same widget example could be ported to Android.

I hope you’ve enjoyed this look at wrapping the battery monitoring service on iOS. Inevitably there have been a lot of new concepts in here, however it does show that we are putting full access to every OS API and external libraries well within reach. There will be no more need for the old externals interface, far less learning to do and no third party build tools or compilers to install. Fortune favors the bold and we hope that you’ll be willing to spend some time learning LiveCode Builder as it matures to this level (with the delivery of Infinite LiveCode). And if you truly don’t feel LiveCode Builder is for you, you’ll still be able to enjoy the benefits of having many of these widgets available, built both by LiveCode Ltd and by other users within our community.

Let’s get it funded!

Appendix – Complete Examples for iOS and Android

Download a pdf document showing complete examples for both iOS and Android.

Mark WaddinghamInfinite LiveCode – An Example

Related Posts

Take a look at these posts

4 comments

Join the conversation
  • Sean Cole - May 16, 2016 reply

    This is beautiful! I can’t wait to see it implemented. I can already thin of a huge number of uses for this. Some were asking about a gaming engine for LC. This could aid in this.

  • Alberto Meguira - May 16, 2016 reply

    Congratulations for this paper. I am not a programer but I have a good idea about future livecode potentials. With infinite livecode I think that we will break resistance to adopt livecode for any size and complexity of projects. Chapeau to all livecode team. Alberto Meguira

  • Paul - May 17, 2016 reply

    If you deliver the equivalent of the once promised Android Externals SDK, I will start being a paying customer again.
    I switched to other toolsets back in 2014 — I needed BLE, Android Beam, Text to speech.

    Paul

  • Greg Anthony - May 18, 2016 reply

    I love where this is headed. Wanted to play with the examples and it appears the examples are missing some of the additional modules that the libraries use.
    Like for Android
    com.livecode.wrapped_apis_v1.livecode.android
    com.livecode.wrapped_apis_v1.android.content.intent

Join the conversation

*