{"id":1855,"date":"2013-10-08T20:15:05","date_gmt":"2013-10-08T08:15:05","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=1855"},"modified":"2013-10-10T18:03:07","modified_gmt":"2013-10-10T06:03:07","slug":"well-behaved-widgetry","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/1855\/","title":{"rendered":"Developing an AppWidget &#8211; Part 3"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">9<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span><h2>Well Behaved Widgetry<\/h2>\n<p>When we left it, <a href=\"https:\/\/www.deltics.co.nz\/blog\/posts\/1791\">my battery widget was working but wasn&#8217;t particularly well behaved<\/a>.  There was nothing much wrong with the functionality, but plenty wrong with the implementation.<\/p>\n<p><!--more--><\/p>\n<p>Despite their impressive specifications, mobile devices have one very limiting factor.  Battery Life.<\/p>\n<p>Indeed, the impressive specifications are part of the reason that battery life can be a problem, but as application developers targeting mobile platforms there are things we can &#8211; and should &#8211; do, to ensure that we don&#8217;t make the problem worse than it needs to be.<\/p>\n<p>So let&#8217;s look at the problems with my battery widget.<\/p>\n<h3>System Scheduled Updates<\/h3>\n<p>For simplicity and convenience, I declared an update interval in the meta-data for my widget.<\/p>\n<p>This is bad for two reasons:<\/p>\n<ol>\n<li>Declared update intervals are subject to a maximum frequency (i.e. they cannot be schedule more frequently than a prescribed limit).  There is a good reason for this, which is the second problem with such declared updates<\/li>\n<li>For updates scheduled in this way, the system will wake the device in order to perform the requested update.  For my widget this is pointless &#8211; if the device is asleep the screen is off, so having an up-to-date battery %&#8217;age displayed is neither here nor there.  But it&#8217;s worse than that. Not only will my widget wake up the device needlessly, but also when it does any other apps or widgets that were waiting patiently for the <em>user<\/em> to wake the device will also potentially take this as their cue to do some work that they otherwise needn&#8217;t<\/li>\n<\/ol>\n<p>All in all, my simple widget is a real trouble maker.<\/p>\n<p>Fortunately there is an alternative to scheduling updates in this way.  Alarms.<\/p>\n<h3>Be Alarmed<\/h3>\n<p>Not to be confused with an &#8220;<em>alarm <strong>clock<\/strong><\/em>&#8220;, on <strong>Android<\/strong> there is a system <strong><a href=\"http:\/\/developer.android.com\/reference\/android\/app\/AlarmManager.html\">AlarmManager<\/a><\/strong> which can be used to scheduled recurring (or one off) events.<\/p>\n<p>Even better, when setting up an alarm using <strong>AlarmManager<\/strong>, we can specify whether or not our alarm is important enough to wake up the device.  If not, and if the device is asleep when our alarm would have been triggered, it will instead be triggered immediately that the device is first awoken after our alarm had elapsed.<\/p>\n<p>And even better still, with the AlarmManager there are no constraints on the frequency of any recurring alarm we may wish to set (other than our own common sense and consideration for others).<\/p>\n<p>Perfect.<\/p>\n<p>So, let&#8217;s adapt the battery widget to use an alarm.<\/p>\n<p>First of all, we set the update period in the meta-data to &#8220;0&#8221; to indicate that we do not wish to receive any system generated updates:<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n    android:updatePeriodMillis=&quot;0&quot;\r\n<\/pre>\n<p>Next, we add a class var (static member) to our <strong>BatteryWidgetProvider<\/strong> class, to hold a reference to the <strong>AlarmManager<\/strong>.  As we shall see, this will both act as a cache for the reference to the <strong>AlarmManager<\/strong> and also serve as a flag that we have an alarm set.<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n    private\r\n      class var fAlarm: AlarmManager;\r\n<\/pre>\n<p>Now we need to extend BatteryWidgetProvider further.  Previously we only responded to <strong>ACTION_APPWIDGET_UPDATE<\/strong> intents, but now we need to respond to two other intents in the widget lifecycle:<\/p>\n<ul>\n<li><strong>ACTION_APPWIDGET_ENABLED<\/strong> &#8211; an intent received when the first instance of the widget is placed on the home screen.  We will use this intent to set our update alarm.<\/li>\n<li><strong>ACTION_APPWIDGET_DISABLED<\/strong> &#8211; an intent received when the last instance of the widget is removed from the home screen.  No prizes for guessing that we will use this intent to <strong>cancel<\/strong> our update alarm.<\/li>\n<\/ul>\n<p>To receive these intents we must add them to the <code>intent-filter<\/code> for the <strong>BatteryWidgetProvider<\/strong> <code>receiver<\/code> entity in the <strong>AndroidManifest<\/strong>.<\/p>\n<pre class=\"brush: xml; highlight: [3,4]; title: ; notranslate\" title=\"\">\r\n      &lt;intent-filter&gt;\r\n        &lt;action android:name=&quot;android.appwidget.action.APPWIDGET_UPDATE&quot; \/&gt;\r\n        &lt;action android:name=&quot;android.appwidget.action.APPWIDGET_ENABLED&quot; \/&gt;\r\n        &lt;action android:name=&quot;android.appwidget.action.APPWIDGET_DISABLED&quot; \/&gt;\r\n      &lt;\/intent-filter&gt;\r\n<\/pre>\n<p>Although not as important for recurring updates, we will leave the <strong>APPWIDGET_UPDATE<\/strong> intent in place to ensure that new widget instances are updated immediately when initially placed.<\/p>\n<p>Now we can override the <code>onEnabled<\/code> and <code>onDisabled<\/code> methods of the <strong>BatteryWidgetProvider<\/strong> class.  These methods are introduced by the <strong>AppWidgetProvider<\/strong> ancestor class &#8211; mapping the intents onto these virtual methods is one of the conveniences it provides.  Handling intents more directly is something we will come to later.<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n      method onDisabled(aContext: Context); override;\r\n      method onEnabled(aContext: Context); override;\r\n<\/pre>\n<p>In the implementation of the onEnabled method, we will obtain a reference to the AlarmManager and establish our alarm.  If we already have a reference to the AlarmManager then our alarm is already in place and does not need to be re-established.<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n  method BatteryWidgetProvider.onEnabled(aContext: Context);\r\n  begin\r\n    if NOT assigned(fAlarm) then\r\n    begin\r\n      fAlarm := aContext.SystemService[Service.ALARM_SERVICE] as AlarmManager;\r\n      fAlarm.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime, 1000, updateIntent(aContext));\r\n    end;\r\n  end;\r\n<\/pre>\n<p>We obtain a reference to the <strong>AlarmManager<\/strong> from the <code>SystemService<\/code> member of a context, in this case the <strong>Context<\/strong> supplied with the Enabled intent.<\/p>\n<p>As with just about everything in Android, <strong>Intents<\/strong> are involved again.<\/p>\n<p>Rather than providing a callback function or any other such contrivance, an alarm is configured with an intent.  In this way you can directly configure an alarm to perform (almost) any intent based action in the system, even going to far as to launch applications if you wish (not advisable &#8211; users might be confused).<\/p>\n<p>In this case the intent supplied to the alarm is build by another method we shall add to the provider class, <code>updateIntent(Context)<\/code>.  It is important that we have a method for reliably constructing the appropriate intent because when we cancel an alarm we do so by identifying the intent whose alarm is to be cancelled, so it <em>must<\/em> match.<\/p>\n<p>But before we get to that, let&#8217;s look at the other aspects of establishing this alarm.<\/p>\n<h3>Alarm Parameters<\/h3>\n<p>First, I am using the <code>setRepeating()<\/code> method to set the alarm.  As the name implies, this will establish a repeating alarm.  The <code>set()<\/code> method establishes a one-time alarm.  The <code>setInexactRepeating()<\/code> method also establishes a repeating alarm, but allows the system to vary the schedule of the alarm slightly, so as to coalesce alarms to maximise efficiency.<\/p>\n<p>The first parameter to this call specified the type of alarm.  There are two basic types, <strong>ELAPSED_REALTIME<\/strong> and <strong>RTC<\/strong>.  ELAPSED_REALTIME alarms are specified in terms of intervals (trigger alarm in 5 seconds time), RTC alarms are specified in terms of actual clock time (trigger alarm at 1pm).<\/p>\n<p>In addition, both types of alarm have a WAKEUP (ELAPSED_REALTIME_WAKEUP \/ RTC_WAKEUP) variant which indicates that the device should be awoken to ensure delivery of the alarm intent at the appropriate time.  I specifically do <strong>not<\/strong> want to do this for this widget.<\/p>\n<p>The next parameter indicates the initial time at which the alarm should fire.  For an elapsed time alarm this should be based on <code>SystemClock.elapsedTime<\/code> (the current-time).  You could apply an offset to this time if you do not need the alarm to fire immediately, but in this case right now is as good a time as any.<\/p>\n<p>The third parameter identifies the repeating interval in milliseconds.  Very straight-forward.<\/p>\n<p>In this case I have set a <strong>ridiculous<\/strong> interval of just 1 second.<\/p>\n<p>Make no mistake.  <strong>This is bonkers<\/strong> !!<\/p>\n<p>Battery level is not going to alter significantly &#8211; if at all &#8211; in such a time frame and by updating so frequently all I am doing is contributing to battery drain.  But I want to be certain that my alarm is firing at this stage.  I will add some variation to the display of the battery percentage so that I can see the updates occuring without having to hang around waiting for the battery level to actually fall (or rise, if charging).<\/p>\n<p>Once I am satisfied that everything is working I will remove that additional visual feedback and set a more appropriate interval.  I might even allow the user to choose one that suits them and their device.<\/p>\n<p>The final parameter is the intent associated with my new alarm.  Actually it is a special sort of Intent called a <strong><a href=\"http:\/\/developer.android.com\/reference\/android\/app\/PendingIntent.html\">PendingIntent<\/a><\/strong>.  So let&#8217;s look at how we construct this in the <code>updateIntent()<\/code> method.<\/p>\n<h3>Building a PendingIntent<\/h3>\n<p>You can think of a PendingIntent as a sort of envelope containing an actual Intent that will be delivered at some point.  Like any envelope that you pop in the post, you have to address it so that the postal service know where to send it.<\/p>\n<p>First let&#8217;s consider the Intent we will trigger with our alarm.<\/p>\n<p>It just so happens that in the case of this widget, I already have an <em>ideal intent<\/em> in mind.  Remember that we implemented the update code for the widget in a <strong>Service<\/strong>, and we start a <strong>Service<\/strong> using&#8230; an <strong>Intent<\/strong>.  We can just use exactly the same specification of intent for our alarm as well.  All that changes is the way we deliver it.<\/p>\n<p>After we have created our <strong>Intent<\/strong>, we then obtain a stamped, addressed envelope and slip our <strong>Intent<\/strong> inside it.  This is achieved using an appropriate method of the <strong>PendingIntent<\/strong> class.<\/p>\n<p>For an <strong>Intent<\/strong> to be broadcast to an arbitrary broadcast receiver, we would use <code>getBroadcast<\/code>.<\/p>\n<p>For an <strong>Intent<\/strong> to be send to an <strong>Activity<\/strong>, we would use <code>getActivity<\/code>.<\/p>\n<p>Since our <strong>Intent<\/strong> is intended [sic] to be delivered to a <strong>Service<\/strong>, we use .. again, no prizes for having guessed already .. the <code>getService<\/code> method.<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n  method BatteryWidgetProvider.updateIntent(const aContext: Context): PendingIntent;\r\n  begin\r\n    var action := new Intent(aContext, typeOf(UpdateService));\r\n    result := PendingIntent.Service[aContext, 0, action, PendingIntent.FLAG_UPDATE_CURRENT];\r\n  end;\r\n<\/pre>\n<p>There are some parameters supplied to the <code>getService<\/code> method (exposed by the magic of <strong>Oxygene<\/strong> syntax as an indexed property called, simply, <code>Service<\/code>).<\/p>\n<p>First is a <strong>Context<\/strong> (a common requirement which I have still yet to fully grasp myself which is why I haven&#8217;t yet explained it).<\/p>\n<p>Next is an arbitrary ID value.  I am not using this so 0 will do.<\/p>\n<p>Then comes the <strong>Intent<\/strong> we wish to put inside the &#8216;envelope&#8217;.<\/p>\n<p>And finally a flag which determines what should happen to any existing <strong>PendingIntent<\/strong> that matches the specification.  In this case I simply update it, though nothing will ever change.<\/p>\n<p>That is almost enough to get our alarm performing updates for us.  There is a change we need to make to the service before it will work however, but before we do that I will finish the changes to the widget provider itself.<\/p>\n<p>I still need to implement <code>onDisabled()<\/code> to cancel any alarm that has been set.<\/p>\n<h3>Cancelling an Alarm<\/h3>\n<p>Cancelling an alarm is very straightforward:<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n  method BatteryWidgetProvider.onDisabled(aContext: Context);\r\n  begin\r\n    if assigned(fAlarm) then\r\n    begin\r\n      fAlarm.cancel(updateIntent(aContext));\r\n      fAlarm := NIL;\r\n    end;\r\n  end;\r\n<\/pre>\n<p>We only cancel the alarm if an alarm has been set which we can determine by the fact that we have a cached reference to the <strong>AlarmManager<\/strong> (in <strong>fAlarm<\/strong>).<\/p>\n<p>We cancel the alarm by calling the <code>cancel()<\/code> method and passing in a <strong>PendingIntent<\/strong>.  Any alarm that is set with a matching <strong>PendingIntent<\/strong> will be cancelled.<\/p>\n<p>Having cancelled the alarm, we <strong>NIL<\/strong> our reference to the <strong>AlarmManager<\/strong> so that if\/when the widget is again placed on the home screen we will know that we need to re-set the alarm in the <code>onEnabled<\/code> method.<\/p>\n<p>All that&#8217;s left is two minor changes to make to the <strong>UpdateService<\/strong> and we&#8217;re done.<\/p>\n<h3>All Part of the Service<\/h3>\n<p>The first and simplest change is to introduce the visual feedback that will help identify that things are working as intended.<\/p>\n<p>I shall use a <em>unit variable<\/em> (an &#8220;<em>implementation global<\/em>&#8221; if you prefer).  I could have used another <strong>class var<\/strong> (static member) on the <strong>UpdateService<\/strong> class, but this seemed like a good opportunity to show once again how Oxygene targets Java without being entirely constrained by the rules of that language, since Java of course doesn&#8217;t support global variables <del>at all<\/del> as directly as this.<\/p>\n<p>But bear in mind, <em>this isn&#8217;t a recommendation<\/em> just a demonstration.<\/p>\n<p>The unit variable will be a simple boolean which I will toggle each time the service builds an update and use the state to alternate the color of the text of the widget (arbitrarily I chose RED and WHITE):<\/p>\n<pre class=\"brush: oxygene; highlight: [3,4,12,17]; title: ; notranslate\" title=\"\">\r\n\r\nimplementation\r\n\r\n  var\r\n    ticktock: Boolean;\r\n\r\n\r\n  function UpdateService.buildUpdate: RemoteViews;\r\n  begin\r\n\r\n    ...\r\n\r\n    ticktock := NOT ticktock;\r\n\r\n    result := new RemoteViews(PackageName, R.layout.widgetlayout);\r\n    result.setTextViewText(R.id.lblInfo, pct.toString + '%');\r\n\r\n    result.setTextColor(R.id.lblInfo, if ticktock then COLOR.RED else COLOR.WHITE);\r\n  end;\r\n<\/pre>\n<p>Finally, we have to make an important change to the <code>onStart<\/code> method of the <strong>UpdateService<\/strong> itself.<\/p>\n<p>First of all, I was following an out of date example when I first implemented my service.  The <code>onStart()<\/code> method is deprecated and we should now implement <code>onStartCommand()<\/code> instead.  This is important because the return value of <code>onStartCommand<\/code> determines how the system manages our service.<\/p>\n<p>The default implementation of <code>onStartCommand()<\/code> calls <code>onStart()<\/code> and returns <strong>START_STICKY<\/strong>, indicating that our service should be left hanging around for as long as possible.  I am guessing this replicates some early, fairly crude system management behaviour from an early Android version.<\/p>\n<p>I want to be a bit more accommodating and return <strong>START_NOT_STICKY<\/strong> since our service is not long-running and doesn&#8217;t need to be kept hanging around and the system should be able to get rid of it if necessary.  The service will simply be restarted by my widget if and when needed.<\/p>\n<p>In the <strong>onStartCommand<\/strong> implementation itself I also need to change the way I update my widget(s).  Previously the service was always started with an intent that came packaged with an array of widget ID&#8217;s identifying the widgets to be updated.  This is still the case when the service is started in response to an <strong>onUpdate()<\/strong> intent, resulting from the placement of a new instance of the widget.<\/p>\n<p>But when the service is started by my alarm, there is no array of widget ID&#8217;s.  The service instead should update all instances of the widget, and so it must use a slightly different version of the AppWidgetManager.updateAppWidget() method, once which identifies the target widgets not by ID but by class name:<\/p>\n<pre class=\"brush: oxygene; highlight: [1,2,3,12,13,15]; title: ; notranslate\" title=\"\">\r\n\r\n  method UpdateService.onStartCommand(aIntent: Intent;\r\n                                      aFlags: Integer;\r\n                                      aStartID: Integer): Integer;\r\n  begin\r\n    var update := buildUpdate;\r\n\r\n    if aIntent.hasExtra('ids') then\r\n    begin\r\n      var ids := aIntent.Extras.IntArray['ids'];\r\n      AppWidgetManager.Instance[self].updateAppWidget(ids, update);\r\n    end\r\n    else\r\n      AppWidgetManager.Instance[self].updateAppWidget(new ComponentName(self, typeOf(BatteryWidgetProvider)), update);\r\n\r\n    result := Service.START_NOT_STICKY;\r\n  end;\r\n<\/pre>\n<p>And there we go.  A much more well behaved widget that will do it&#8217;s best to not drain the battery it is so carefully monitoring for us.<\/p>\n<p>Well, not quite everything.<\/p>\n<p>There is (at least) one more refinement we can make.  For those not already au fait with such things, here&#8217;s a clue: <em>Just because the screen on your device is off, does not mean that the device is necessarily yet asleep<\/em>.<\/p>\n<p><strong>Update:<\/strong> <em>This morning I also discovered a bug which is that my widget update alarm did not get re-set when my phone was awoken from what I presume was a deep sleep state that it entered over-night.  I have yet to figure out why this is, but I suspect that my service and broadcast receiver are being cleaned up by the system.  I am being a little bit <strong>too<\/strong> conservative somewhere.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">9<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> Well Behaved Widgetry When we left it, my battery widget was working but wasn&#8217;t particularly well behaved. There was nothing much wrong with the functionality, but plenty wrong with the implementation.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[212,4,180],"tags":[230,153,226,181,231,223],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-tV","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":1791,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1791\/","url_meta":{"origin":1855,"position":0},"title":"Developing and Debugging an AppWidget &#8211; Part 2","date":"30 Sep 2013","format":false,"excerpt":"In the first instalment of this series, I implemented the basic framework of a new appwidget and established a means by which I could debug the widget code. Now it's time to add some code worth debugging. You may recall that the aim of my widget is to display the\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screenshot_2013-09-30-20-03-27.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":1873,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1873\/","url_meta":{"origin":1855,"position":1},"title":"Developing an AppWidget &#8211; Part 5","date":"13 Oct 2013","format":false,"excerpt":"In my previous post I explained how I believed I had solved a problem with my widget, only to discover that it created a different problem in the process. I had believed that IntentService based services were long-lived, but in fact this is not the case. However, the change remains\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1774,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1774\/","url_meta":{"origin":1855,"position":2},"title":"Developing and Debugging an AppWidget &#8211; Part 1","date":"29 Sep 2013","format":false,"excerpt":"With my external HDD trials and tribulations behind me, I have finally been able to complete a project I started last weekend - implementing an Android AppWidget. Along the way I have learned some more about both Android and Oxygene and what can be involved with working with the two\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"Adding the xml folder","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-09-29-at-15.08.09-.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":1869,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1869\/","url_meta":{"origin":1855,"position":3},"title":"Developing an AppWidget &#8211; Part 4","date":"10 Oct 2013","format":false,"excerpt":"Keep On Running... Some may have noticed that I updated my previous post with a footnote describing an issue with my battery widget not resuming it's updates after the phone went into (and came out of) deep sleep. I got to the bottom of that and can now happily report\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1817,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1817\/","url_meta":{"origin":1855,"position":4},"title":"Getting the Battery Level on Android With Delphi","date":"01 Oct 2013","format":false,"excerpt":"Over the past few days I posted a two part series showing how to obtain the current battery level as part of the implementation of an Android AppWidget using Oxygene. As far as I can tell AppWidgets simply aren't possible using Delphi but reading the battery is quite straightforward Android\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2296,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2296\/","url_meta":{"origin":1855,"position":5},"title":"Sign Here Please&#8230; (Using This Specific Pen)","date":"01 Sep 2014","format":false,"excerpt":"My original Android version of TXT-2-PARK has been in the Google Play Store for a little over a week now. But it was only on Saturday that I decided to install the release version onto my phone from the store itself, and found a problem. The application would download just\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/1855"}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=1855"}],"version-history":[{"count":10,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/1855\/revisions"}],"predecessor-version":[{"id":1870,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/1855\/revisions\/1870"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=1855"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=1855"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=1855"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}