Many organisations are offering rich Linked Data stores now that you can interrogate with the SPARQL language. This data might be interesting for the mobile app developer to work with so it would be great to be able to experiment with this data in Google App Inventor for Android applications. At the moment you cannot do this directly as App Inventor only offers quite limited functionality to interact with the web, however with the help of a server side "bridging script" we can close that divide and send a SPARQL query from inside the application and deal with the results we get back.
So what do I mean by a "bridging script"? The idea is that this script acts as a translation layer between App Inventor and the SPARQL end point. The best way at the moment to deal with web data on App Inventor at the moment is to use the tinywebdb component, but unfortunately this component can only deal with a quite simple incoming data structure. SPARQL endpoints do not natively generate this structure so our translation layer will take the results and reformat it in a way that tinywebdb can use. This of course migth have a side effect of slightly restricting what you can do with the Linked Data as you will lose some of its richness, but it is better than not being able to use it at all!
The server side bridging script
The bridging script is based on my earlier blog post: An approach to consuming Linked Data with PHP but modified to provide the new data structure. Each piece of information in the results of a SPARQL query is called an RDF triple. This means that the information consists of three things: the subject (similar to a SQL column heading), the predicate (what type of information it is e.g. integer, string, spaceship) and the value itself. The information that tinywebdb can accept currently has to be in a much simpler format, basically just an array of values and not even keyed values.
With this in mind the bridging script does a fairly brutal conversion of the data from the SPARQL query to a JSON encoded format that tinywebdb can understand. Currently every value is converted into a string, regardless of type (though URLs of predicates are intact) , the subject is removed and the data is returned in order. What you end up with is an list/array of lines each with elements relating to each value you asked for. You could of course build on this script to include logic that can more effectively handle your requirements.
In this example I will work on the basis that you are installing these files to a location called /ldbridge on your webserver, so this would provide a ServiceURL for your tinywebdb component that would be: http://www.example.com/ldbridge. At this location the component expects to find a file named "getvalue" but our file will be called "getvalue.php" to make sure the server processes the PHP correctly, so a rewrite rule is needed on Apache. Put this in the .htaccess file:
RewriteEngine On
RewriteBase /ldbridge
RewriteRule ^getvalue$ getvalue.php
Now for the PHP script which accepts an incoming SPARQL query from a App Inventor as the "tag" and then returns the results as a JSON object under the tag "results". This provides a data structure that is reasonably easy to work with in the Blocks Editor. This version of the script is hardcoded to use the Open University's Linked Data service but you can easily change this by using a new value for ENDPOINT_URL.
/**
* Linked Data to App Inventor bridging script
* By Liam Green-Hughes
*/
define("ENDPOINT_URL", "http://data.open.ac.uk/query?query=%s");
// Perform an HTTP request using CURL
function request($url){
// is curl installed?
if (!function_exists('curl_init')){
die('CURL is not installed!');
}
// get curl handle
$ch= curl_init();
// set request url
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_USERAGENT, "LD To App Inventor Bridge/1.0");
// return response, don't print/echo
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
// Get the results for a LD query in a simple format
function do_LD_Query($sparql) {
$requestURL = sprintf(ENDPOINT_URL, urlencode($sparql));
$response = request($requestURL);
// container for our data
$data = array();
// initialise SimpleXML object and load it with data
$xml = simplexml_load_string($response);
if ($xml === FALSE) {
return $response;
}
// get the
$results = $xml->results;
// loop through
if (isset($results->result)) {
foreach($results->result as $result) {
$line = array();
foreach ($result->binding as $binding) {
if (isset($binding->uri)) {
$line[] = (string) $binding->uri;
}
else {
// could pick up xsd data type for right cast
$value = (string) $binding->literal;
if ((string) $binding["name"] == 'error-message') {
$value = "ERROR: ".$value;
}
$line[] = $value;
}
}
$data[] = $line;
}
}
return $data;
}
// get the results in a tinywebdb friendly format
$results = do_LD_Query($_POST['tag']);
// encode them for transmission
$retval = array("VALUE", 'results', $results );
// send to the client
echo json_encode($retval);
?>
Building an example app
A simple app can be built to test this bridging script out. Start a new app in AppInventor and call it something like "LD Test". In the designer view drag a button, a label and a tinywebdb component to the screen. Rename the button as "btnGo", and set the ServiceURL property of the tinywebdb component to the location of your script. This app will run a query and populate the label with the results. A bit of a rough and ready demo, but it shows the concept in action.
Open the Blocks Editor, go to My Blocks and open the btnGo drawer. Drag the when btnGo.click do block to the work area. Open the drawer for Label1 and drag the set Label1.Text to block and click it inside the when btnGo.Click do block. Under Built In go to Text and drag a text block to the set Label1.Text to block and change the text to "Getting results...". Now go to My Blocks -> TinyWebDb1 and drag the call TinyWebDB1.GetValue tag block undernath the set Label1.Text block. Create a new text block and copy paste in your SPARQL query. Here is an example query that lists all courses related to French:
PREFIX aiiso:
PREFIX rdf:
PREFIX dc:
PREFIX xsd:
SELECT ?course ?code ?name ?type ?subject
FROM
WHERE {
?course aiiso:code ?code .
?course aiiso:name ?name .
?course rdf:type ?type .
?course dc:subject
}
ORDER BY ?code
You should now have a block that looks like this:
As ever in programming we need to handle the possibility of an error, especially with a remote service. Drag a when TinyWebDb1.WebServiceError message block to the work area. Inside this block do set Label1.Text to and go to My Definitions and drag the value message block and plug it in. You error handling block should look like this:
Now for the exciting bit, we've sent off the request and dealt with the possibility of an error. Now it is time to handle incoming results. Drag the when TinyWebDb1.GotValue block to the work area. Inside this we will need two loops, one to loop through each line, the other to loop through each element in the line. Go to Built-In -> Control and drag a foreach block inside the TinyWebDb1.GotValue block. Rename the variable in the foreach as line then go to My Blocks -> My Definitions and drag the value valueFromWebDB block to the in list socket of the foreach loop. Drag another foreach block inside the first foreach block. Rename the variable as "element" and plug in the value line block into the in list socket.
In this demo I am just dumping the results to the label component. In a real application you could do something more sophisticated like use the values for a ListPicker. Drag the set Label1.text to block inside the nested foreach loop and go to Built-in -> Text then drag a join block and insert it into the socket for the set Label1.text to block. In the left side gap in the join block should go a Label1.Text block and in the right a value element block. You should now have a section that looks like this:
When you come to run the app your SPARQL query will be sent through the bridging script and the data returned will be handled by the blocks you put together. Note that this might not work very well in the emulator at the moment so it is best to try this with an real Android device. The results are not pretty but show the idea in action and provide a starting point.